Reputation: 111
I have a question of functionality that I am uncertain about. I'm not stuck on code implementation, so there is no need to share code for this question. I use
layout.removeWidget
widget.deleteLater()
a couple/few times in a project I'm building. I once read here on SO that deleteLater()
does not function as described when deleting a widget with child widgets. The comment said that child widgets do not get deleted correctly, and will stay in memory, which could lead to memory bloat/leaks. Simply put (my question), is this true?
The docs mention nothing of this that I could find. And it was just one comment from years back, that was actually written for PyQt5 (or 4), I believe. So, have any of you guys done any tests on this? Is this a bug in older versions of PyQt that has been fixed, or was the commentor outright wrong?
As you can see from my question, my issue isn't how to write something, it's about behind the scenes functionality of deleteLater()
.
Upvotes: 1
Views: 1358
Reputation: 48424
First of all, it is important to remember that PyQt (as PySide) is a binding to Qt, which is written in C++.
All Qt objects and functions are accessed from python using wrappers.
When a Qt object is created from Python, there are actually two objects:
The lifespan of those two objects may not always coincide. In fact, the C++ object can be destroyed while the python reference remains, or the other way around.
deleteLater()
is guaranteed to destroy the C++ objects along with any object for which they have ownership (including indirect ownership for grand[grand, ...]children). Note that the object is not destroyed immediately, but only as soon as control returns to the event loop: then the object emits the destroyed()
signal and after that all its children are destroyed along with it, recursively.
This will not delete the python reference, if it existed. For instance:
class MyLabel(QLabel):
def __init__(self, value, parent=None):
super().__init__(value, parent)
self.value = value
If you keep a python reference to an instance of the class above, you will still be able to access its self.value
even after the object has been destroyed with deleteLater()
. If you try to access any Qt functions or properties, instead, you'll get an exception:
>>> print(mylabel.value)
'some value'
>>> print(mylabel.text())
RuntimeError: wrapped C/C++ object of type MyLabel has been deleted
So, the python object will obviously keep using memory resources until it get garbage collected (its reference count becomes 0). This can be a problem if you keep references to objects that have large memory footprint on their python side (i.e., a large numpy array).
Deleting a python reference doesn't guarantee the destruction of the Qt object, as it only deletes the python object. For instance:
>>> parent = QWidget()
>>> label = MyLabel('hello', parent)
>>> parent.show()
>>> del label
>>> print(parent.findChild(QLabel).text())
'hello'
This is because when an object is added to a parent, that parent takes ownership of the child.
Note that Qt objects that have no parent will also get destroyed when all python references are destroyed as well.
That is the reason of one of the most common questions here on SO, when somebody is trying to create another window using a local reference and without any parent object, but the window is not shown because it gets immediately garbage collected.
Note that this is valid for Qt6 as it is for Qt5 and as it was for Qt4, and it is also for their relative python bindings.
Considering what written in the first part:
>>> parent = QWidget()
>>> label = MyLabel('hello', parent)
>>> parent.show()
>>> del parent
>>> print(label.value)
'hello'
>>> print(label.text())
RuntimeError: wrapped C/C++ object of type MyLabel has been deleted
So, what must be always kept in mind is where objects live, including their C++ and python properties and attributes. If you're worried about memory usage for standard (not python-subclassed) widgets, deleteLater()
is always guaranteed to release the memory resources of those widgets, and you only need to ensure that no remaining python reference still exists (but that would be true for any python program).
Finally, some considerations:
Upvotes: 3