Issue
I have a layout that I add a lot of custom widgets to with something like layout.addWidget(widget)
. Later I want to remove all those custom widgets and add new ones. I'm confused on the best way to do this when it comes to deleteLater
and setParent(None)
. For example, here's my cleanup function that's called for all the widgets in the layout:
def _removeFilterWidgetFromLayout(self, widget):
"""Remove filter widget"""
self._collection_layout.removeWidget(widget)
widget.setParent(None)
widget.deleteLater()
I have a suspicion that not all of these lines are needed to properly cleanup the memory used by the widget. I'm unsure of what is happening with the C++ and Python references to widget
.
Here's what I understand about the C++ and Python references to QObjects in PyQt.
I believe that when you add a widget to a layout the widget becomes a child of the layout. So, if I call removeWidget
then the parent relationship is broken so I have to clean up the C++ and Python reference myself since the widget has no other parent. The call to setParent
is an explicit way of removing the parent relationship with the layout. The call to deleteLater
is meant to take care of the C++ reference.
The Python reference is garbage collected because the widget
variable goes out of scope, and there are no other Python objects pointing to widget
.
Do I need to call setParent
and deleteLater
or would deleteLater
be enough to properly clean this up?
As a side note, I've found that calling setParent(None)
is a very expensive function call in this scenario. I can greatly speed up my entire cleanup process by removing this call. I'm not sure if deleteLater
is enough to clean everything up correctly. Here's my profiling output from line_profiler
:
Line # Hits Time Per Hit % Time Line Contents
==============================================================
2167 @profile
2168 def _removeFilterWidgetFromLayout(self, widget):
2169 """Remove filter widget"""
2170
2171 233 1528 6.6 1.0 self._collection_layout.removeWidget(widget)
2172 233 143998 618.0 97.9 widget.setParent(None)
2173 233 1307 5.6 0.9 widget.deleteLater()
When using PyQt4 is there an 'accepted' way to do this cleanup? Should I use setParent
, deleteLater
, or both?
Solution
Probably the easiest way to see what's actually going on is to step through things in an interactive session:
>>> parent = QtGui.QWidget()
>>> child = QtGui.QWidget()
>>> layout = QtGui.QHBoxLayout(parent)
>>> layout.addWidget(child)
>>> child.parent() is layout
False
>>> child.parent() is parent
True
So the layout does not become the parent of the widget. This makes sense, because widgets can only have other widgets as parents, and layouts are not widgets. All widgets added to a layout will eventually have their parents reset to the parent of the layout (whenever it gets one).
>>> item = layout.itemAt(0)
>>> item
<PyQt4.QtGui.QWidgetItem object at 0x7fa1715fe318>
>>> item.widget() is child
True
Since there is no parent/child relationship bewteen layouts and the widgets they contain, a different API is needed for access to the underlying objects. The items are owned by the layout, but the ownership of the underlying objects remains unchanged.
>>> layout.removeWidget(child)
>>> child.parent() is parent
True
>>> layout.count()
0
>>> repr(layout.itemAt(0))
'None'
>>> item
<PyQt4.QtGui.QWidgetItem object at 0x7fa1715fe318>
At this point, the layout has deleted its item (because it had ownership of it), and thus no longer holds any references to the contained widget. Given this, it is no longer safe to do much with the python wrapper for the item (the interpreter would probably crash if we tried to call any of its methods).
>>> child.deleteLater()
>>> parent.children()
[<PyQt4.QtGui.QHBoxLayout object at 0x7fa1715fe1f8>]
>>> child.parent()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: wrapped C/C++ object of type QWidget has been deleted
>>>
Since we still have ownership of the child widget, we can call deleteLater
on it. And as can be seen from the traceback, this will delete the underlying C++ object, but its python wrapper object will be left behind. However, this wrapper will (eventually) be deleted by the garbage collector, once any remaining python references to it are gone. Note that there is never any need to call setParent(None)
during this process.
One final point: the above interpreter session is slightly misleading, because the event-queue is processed every time a line is executed. This means the effects of deleteLater
are seen immediately, which would not be the case if the code was run as a script. To get immediate deletion in a script, you would need to use the sip
module:
>>> import sip
>>> sip.delete(child)
Answered By - ekhumoro
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.