Search code examples
pythonqtpyqtpyside

Is deleteLater() necessary in PyQt/PySide?


Since there is already a garbage collector in Python, is deleteLater() necessary in PyQt/PySide?


Solution

  • It depends what you mean by "necessary".

    An application could potentially consume a lot of memory if (for example) care is not taken when closing widgets. The QObject-based classes are designed to be (optionally) linked together in a hierarchy. When a top-level object is deleted, Qt will automatically delete all its child objects as well. However, when closing widgets (which are sub-classes of QObject), automatic deletion will only happen if the Qt.WA_DeleteOnClose attribute is set (which, by default, it usually isn't).

    To illustrate, try repeatedly opening and closing the dialog in this demo script, and watch how the global list of objects grows:

    import sys
    from PyQt5 import QtCore, QtWidgets
    
    class Window(QtWidgets.QWidget):
        def __init__(self):
            super().__init__()
            self.checkbox = QtWidgets.QCheckBox('Delete')
            self.button = QtWidgets.QPushButton('Open', self)
            self.button.clicked.connect(self.openDialog)
            layout = QtWidgets.QHBoxLayout(self)
            layout.addWidget(self.checkbox)
            layout.addWidget(self.button)
    
        def openDialog(self):
            widget = QtWidgets.QDialog(self)
            if (self.checkbox.isChecked() and
                not widget.testAttribute(QtCore.Qt.WA_DeleteOnClose)):
                widget.setAttribute(QtCore.Qt.WA_DeleteOnClose)
                for child in self.findChildren(QtWidgets.QDialog):
                    if child is not widget:
                        child.deleteLater()
            label = QtWidgets.QLabel(widget)
            button = QtWidgets.QPushButton('Close', widget)
            button.clicked.connect(widget.close)
            layout = QtWidgets.QVBoxLayout(widget)
            layout.addWidget(label)
            layout.addWidget(button)
            objects = self.findChildren(QtCore.QObject)
            label.setText('Objects = %d' % len(objects))
            print(objects)
            widget.show()
    
    if __name__ == '__main__':
    
        app = QtWidgets.QApplication(sys.argv)
        window = Window()
        window.setGeometry(500, 300, 100, 50)
        window.show()
        sys.exit(app.exec_())
    

    With PyQt/PySide, there are two aspects to object ownership: the Python part, and the Qt part. Often, removing the last Python reference to an object won't be enough to fully clean up, because there could still be a reference held on the Qt side.

    In general, Qt tends not to implicity delete objects. So if your application creates and removes lots of QObjects (or opens and closes lots of QWidgets), you may need to take steps to delete them explicitly if memory usage is a concern.

    UPDATE:

    Just to add to the points above on object ownership. Sometimes, it is possible to hold a Python reference to an object, whilst the Qt part gets deleted. When this happens, you will see an error like this:

    RuntimeError: underlying C/C++ object has been deleted

    Usually, the Qt documentation will give some hints about when this might happen. For instance, QAbstractItemView.setModel gives this warning:

    The view does not take ownership of the model unless it is the model's parent object...

    This is telling you that you must either keep a Python reference to the object, or pass a suitable parent object to the object's constructor, because Qt will not always automatically reparent it.