如何删除 QTreeWidgetItem

12

有几个网页显示删除或使用QTreeWidget.clear可以删除QTreeWidgetItem。但是,我的代码样例似乎没有实现此功能。我有做错什么吗?

#!/usr/bin/python
import sys
from PySide.QtGui import QApplication, QWidget, QTreeWidget, QTreeWidgetItem
#from PyQt4.QtGui import QApplication, QWidget, QTreeWidget, QTreeWidgetItem # Result was the same with `PySide`
import time

class TreeWidgetItemChild(QTreeWidgetItem):
    def __init__(self):
        super(TreeWidgetItemChild, self).__init__()
        print 'TreeWidgetItemChild init'

    def __del__(self):
        print 'TreeWidgetItemChild del'

def test_QTree_clear_children():
    tree = QTreeWidget()
    tree.setHeaderLabel('funksoul')
    i = TreeWidgetItemChild()    
    tree.addTopLevelItem(i)    
    print 'Before clearing'
    #tree.clear()                  # Didn't call destructor (__del__)
    #tree.removeItemWidget (i, 0)  # Didn't call destructor
    #i.__del__()                   # Called destructor but it's called again afterward
    del i                          # Didn't call destructor
    time.sleep(1)
    print 'After clearing'

if __name__ == '__main__':
    app = QApplication(sys.argv)
    test_QTree_clear_children()

打印为:

TreeWidgetItemChild init
Before clearing
After clearing
TreeWidgetItemChild del

在我看来,TreeWidgetItemChild是在进程终止时被删除的,而不是由于我的任何删除操作。


不确定Python是否也是这样,但在C++中删除QListWidgetItem将使其自动从列表中移除(析构函数会执行此操作)。 - sashoalm
4个回答

16

Python内存管理 / 删除对象方面与C++不同。Python拥有一个垃圾回收器(GC),它可以自动管理对象的销毁。当一个对象的引用计数达到零时,垃圾回收器才会将其销毁。

del i 的作用仅仅是“将引用计数减一”,它从未直接调用__del__。只有当一个对象的引用计数为零并且即将被垃圾回收时,__del__方法才会被调用。(尽管对于CPython而言是如此,但并不保证每个实现都是这样,这取决于GC实现。因此,您根本不能依赖__del__

简而言之,__del__的调用时间是模糊的。您永远不应该直接调用__del__(或任何其他__foo__特殊方法)。事实上,出于上述原因,通常应避免使用__del__(除非有必要)。

除此之外,还有另一个问题。

tree.removeItemWidget(i, 0)

这不会从QTreeWidget中删除项目。顾名思义,它将从项中删除一个小部件,而不是QTreeWidgetItem本身。这是setItemWidget方法的对应项,而不是addTopLevelItem方法。

如果您需要从树中移除特定的项目,您应该使用takeTopLevelItem

tree.takeTopLevelItem(tree.indexOfTopLevelItem(i))

tree.clear()是可以的。它会从树中删除每个顶级项目。


1
根据我对Python垃圾回收Qt对象的一些测试,我发现即使它没有引用,Python也无法将其回收。 - Bleeding Fingers
@hus787:哦?你是怎么测试的? - Avaris
1
@hus787:不,它不是“无法访问的”。您可以通过父级访问它(例如parent_object.children())。如果在删除之前检查gc.get_referrers,除了您给定的名称外,您还会看到父级也在其中。 - Avaris
1
@hus787:希望这能解决混淆问题:http://bpaste.net/show/tiHqOiU2NNJ87gpQIUnS/。有一个 QPushButton,它创建了两个 QDialog 并将它们存储为属性。其中一个对话框没有父级,另一个以 QPushButton 为父级。单击按钮将删除属性(即删除这些引用)。无父级的对话框将被垃圾回收,因为它没有其他引用。有父级的对话框仍然保持打开状态,因为按钮仍然存在并保留引用。即使 self.window 不再可用。 - Avaris
啊,所以方法是 takeTopLevelItem()。我想知道,当API被命名为狗屎时,引用有什么好处。 - K3---rnc
显示剩余4条评论

4
通过调用 del i,您只是删除了引用,而不是实际的C++对象(referent),也就是说,并没有删除对象本身。
将你的 TreeWidgetItemChild.__del__ 函数改为:
def __del__(self):
    treeWidget = self.treeWidget()
    #removing the QTreeItemWidget object
    treeWidget.takeTopLevelItem(treeWidget.indexOfTopLevelItem(self))
    print 'TreeWidgetItemChild del'

4
作为对Avaris精彩回答的一篇结语,让我们来探讨一个更加通用的方法,适用于所有小部件和小部件项(而不仅仅是顶级树形小部件项)。这个所谓的香格里拉是否太美好而不可信呢?
引用马里奥的话:"Waaaa! Let's-a-go!"

我们开始吧

具体而言,如果您的项目利用了:
  • PySide2, import the shiboken2 module and pass each tree widget item to be deleted to the shiboken2.delete() function ala:

    # Well, isn't that nice. Thanks, Qt Company.
    from PySide2 import shiboken2
    
    # Add this item to this tree.
    tree = QTreeWidget()
    item = TreeWidgetItemChild()
    tree.addTopLevelItem(item)
    
    # Remove this item from this tree. We're done here, folks.
    shiboken2.delete(item)
    
  • PyQt5, import the sip module and pass each tree widget item to be deleted to the sip.delete() function ala:

    # Well, isn't that not-quite-so-nice. You are now required to import any
    # arbitrary PyQt5 submodule *BEFORE* importing "sip". Hidden side effects are
    # shameful, of course, but we don't make the rules. We only enforce them. For
    # detailed discussion, see:
    #
    #     http://pyqt.sourceforge.net/Docs/PyQt5/incompatibilities.html#pyqt-v5-11
    #
    # If your project requires PyQt5 >= 5.11, the following compatibility hack may
    # be safely reduced to the following one-liner:
    #
    #     from PyQt5 import sip
    from PyQt5 import QtCore
    import sip
    
    # Add this item to this tree.
    tree = QTreeWidget()
    item = TreeWidgetItemChild()
    tree.addTopLevelItem(item)
    
    # Remove this item from this tree.
    sip.delete(item)
    

绝不可能出现问题

是的,在所有平台和(PyQt5|PySide2)版本下,它都表现如预期。Python特定的sip.delete()shiboken2.delete()方法是对底层C++delete运算符的高级包装——并且完全相同。在QTreeWidgetItem实例的情况下,这会重现C++的行为,即立即将传递的项从其父树中删除。

是的,这既辉煌又靠不住。感谢alexisdm其他地方的相关答案,为这个过度修饰的回答提供了推动力。荣耀归于alexisdm


3
你混淆了树节点和给定项可以包含的小部件。
以下示例创建一个QTreeWidget并向其添加两个项目:顶级项目和嵌套项目。去掉注释后,您可以看到它们都从树中删除。
#!/usr/bin/env python
import sys
from PyQt4.QtGui import *

class MyMainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MyMainWindow, self).__init__(parent)
        self.tree = QTreeWidget(self)
        self.setCentralWidget(self.tree)
        self.tree.setHeaderLabel('funksoul')
        i = QTreeWidgetItem(self.tree, ['top level'])   
        self.tree.addTopLevelItem(i)
        j = QTreeWidgetItem(i ,['nested level'])
        #i.takeChild(0)
        #self.tree.takeTopLevelItem(0)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    ui = MyMainWindow()
    ui.show()
    sys.exit(app.exec_())

要从树中删除这两种项目,您需要知道其索引。如果您拥有要删除的项目的引用,可以使用indexOfTopLevelItemindexOfChild函数获取相应的索引。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接