12

Several webpages say that QTreeWidgetItem can be deleted by deleting or QTreeWidget.clearing. But my code sample below doesn't seem to do so. Am I doing anything wrong?

#!/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()

Printed as:

TreeWidgetItemChild init
Before clearing
After clearing
TreeWidgetItemChild del

Looks to me TreeWidgetItemChild gets deleted upon the termination of process, not by any of my deletion actions.

IsaacS
  • 3,551
  • 6
  • 39
  • 61
  • Not sure about python, but in C++ deleting a QListWidgetItem will make it remove itself from the list, too (the destructor does this). – sashoalm Oct 25 '12 at 06:57

4 Answers4

16

Python is different from C++ in the sense of memory management/deleting objects. Python has a garbage collector (GC) that manages destroying of the objects automatically. That occurs when the reference count of an object reaches zero.

del i only means 'decrement the reference count by one'. It never results in a direct call to __del__. __del__ of an object is only called when reference count reaches to zero and is about to be garbage collected. (Although this is true for CPython, it's not guaranteed for every implementation. It depends on the GC implementation. So you should not rely on __del__ at all)

Keeping story short, the call time of __del__ is ambiguous. You should never call __del__ (or any other __foo__ special methods) directly. In fact, for the reasons above you should rather avoid the use of __del__ at all (usually).

Apart from that, there is another issue.

tree.removeItemWidget(i, 0)

This does not remove an item from QTreeWidget. As the name suggests, it removes a widget from an item, not the QTreeWidgetItem. It's counterpart to the setItemWidget method, not the addTopLevelItem method.

If you need to remove a specific item from the tree, you should use takeTopLevelItem.

tree.takeTopLevelItem(tree.indexOfTopLevelItem(i))

tree.clear() is fine. It will remove every top level item from the tree.

Avaris
  • 35,883
  • 7
  • 81
  • 72
  • 1
    Based on some testing I did with python garbage collection of Qt object I found that python couldn't collect them in spite it having zero referees. – Bleeding Fingers Oct 26 '12 at 13:19
  • @hus787: Oh? How did you test it? – Avaris Oct 26 '12 at 14:10
  • so I developed this small GUI app(Qt based) and instantiated it by calling a python function which also returned its reference name(`plugins.foo.bar.some_random_string` whose parent is the main maya window). Then without closing/hiding the window, calling `del plugins` or `del plugins.foo.bar.some_random_string`, and thereafter even calling `gc.collect`, did not cause the window to disappear i.e. get destroyed by python's garbage collection. `plugins` and `foo` are packages, `bar` is a module and `some_random_string` is the window object itself. – Bleeding Fingers Oct 26 '12 at 22:08
  • @hus787: Huh? Why would python garbage collect that window? If it has a parent, then it has a reference. That parent keeps a reference. It doesn't matter whether there are explicit names pointing to it or not. Its reference count is not zero. – Avaris Oct 26 '12 at 22:33
  • First of all place forgive my ignorance, I am new to the garbage collection phenomenon. So according to your statement should I expect to find that window in `gc.garbage` and consequently get its referrer through `gc.get_referrers`? Or would you suggest posting a new question about this? – Bleeding Fingers Oct 27 '12 at 11:02
  • 1
    @hus787: No, it's not 'unreachable'. You can reach it through the parent (e.g. `parent_object.children()`). And if you check the `gc.get_referrers` before you delete, in addition to the name you given, you'll see the parent is there too. – Avaris Oct 27 '12 at 18:50
  • 1
    @hus787: I hope this clears the confusion: http://bpaste.net/show/tiHqOiU2NNJ87gpQIUnS/ There is a `QPushButton` that creates two `QDialog`s and stores them as attributes. One dialog is parentless, one has the `QPushButton` as parent. Clicking on the button will remove the attributes (i.e. deleting those references). Parentless dialog will be then garbage collected because it has no other references. Parented one stays open because the button is still there and it keeps a reference. Even though `self.window` is no more available. – Avaris Oct 27 '12 at 19:24
  • Thanks for the clarification. You were right the main window(parent) was in fact listed in `gc.get_referrers`, although without any explicit name, hence stopping the widget/window from getting collected. Thank-you once again. – Bleeding Fingers Oct 30 '12 at 08:05
  • Ahh, so the method is `takeTopLevelItem()`. I wonder, what good is a reference when the API is named shit. – K3---rnc Feb 14 '16 at 23:34
4

By calling del i you are just deleting the reference not the actual C++ Object(referent) it refers to, not the object itself.

Change your TreeWidgetItemChild.__del__ function to:

def __del__(self):
    treeWidget = self.treeWidget()
    #removing the QTreeItemWidget object
    treeWidget.takeTopLevelItem(treeWidget.indexOfTopLevelItem(self))
    print 'TreeWidgetItemChild del'
Bleeding Fingers
  • 6,993
  • 7
  • 46
  • 74
4

As an epilogue to Avaris' excellent answer, let's flog an even more general-purpose approach applicable to all widgets and widget items (rather than merely top-level tree widget items). Is this supposed Shangri-La too good to be true?

To quoth the Mario: "Waaaa! Let's-a-go!"

Here We-A-Go

Specifically, if your project leverages:

  • 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)
    

Nothing Could Possibly Go Wrong

Yes, this behaves as expected under all platforms and (PyQt5|PySide2) releases. The Python-specific sip.delete() and shiboken2.delete() methods are high-level wrappers around the underlying C++ delete operator – and operate exactly identically. In the case of QTreeWidgetItem instances, this reproduces the C++ behaviour of immediately removing the passed item from its parent tree.

Yes, it is both glorious and sketchy. Thanks to alexisdm's relevant answer elsewhere for the motivational impetus behind this overwrought answer. Glory be to alexisdm.

Cecil Curry
  • 9,789
  • 5
  • 38
  • 52
3

You're confusing tree items (i.e., tree nodes) with the widgets that a given item can contain.

The following example creates a QTreeWidget and adds two items to it: a top level item and a nested one. Removing the comments you can see how both of them are removed from the tree.

#!/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_())

For removing both types of items from the tree you need the items index. If you have a reference to the item you want to remove you can get the corresponding index with the indexOfTopLevelItem and indexOfChild functions.

Vicent
  • 5,322
  • 2
  • 28
  • 36