在PyQt/PySide中,deleteLater()是必要的吗?

18

既然Python中已经有垃圾收集器,那么在PyQt/PySide中需要deleteLater()吗?

2个回答

23
取决于你所说的“必要”。如果(例如)在关闭小部件时没有注意,应用程序可能会潜在地消耗大量内存。基于QObject的类被设计为可以选择地链接到一个层次结构中。当删除一个顶级对象时,Qt也会自动删除所有它的子对象。然而,当关闭小部件(它们是QObject的子类)时,只有在设置了Qt.WA_DeleteOnClose属性时才会自动删除(默认情况下通常不会)。为了说明这一点,请尝试反复打开和关闭此演示脚本中的对话框,并观察全局对象列表如何增长:
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_())

使用PyQt/PySide时,对象所有权有两个方面:Python部分和Qt部分。通常情况下,仅删除对象的最后一个Python引用是不足以完全清除它的,因为Qt侧可能仍然保留有引用。

一般来说,Qt倾向于隐式删除对象。因此,如果您的应用程序创建和删除大量QObjects(或打开和关闭大量QWidgets),如果内存使用是一个问题,您可能需要采取措施显式地删除它们。

更新:

只是补充上述关于对象所有权的要点。有时,可能会持有一个Python引用到一个对象,而Qt部分被删除。当这种情况发生时,您将看到如下错误:

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

通常,Qt文档会给出一些关于何时可能发生这种情况的提示。例如,QAbstractItemView.setModel 给出了以下警告:

除非它是模型的父对象,否则视图不会拥有模型...

这是在告诉你,你必须要么保留一个Python引用指向该对象,要么将一个合适的父对象传递给该对象的构造函数,因为Qt不会总是自动重设其父对象。

1
  1. 我认为在 PyQt/PySide 或 Python 部分没有区别,只要对象被引用,它就不会被清除。你不这么认为吗?
  2. 此外,WA_DeleteOnClose 适用于我们可以关闭的小部件,但对于像 QNetworkReply 和 QDialog 这样的 QOBject,在此处关闭但未被删除,deleteLater() 是适当的,对吧?
- iMath
(1) 是的,但请查看我的回答更新以获取一个重要的例外。(2) 是的,“WA_DeleteOnClose”只是“可能”有时无法正确清除的“一个”常见示例。对于QObject而言,如果它们没有父级和没有Python引用,它们将被自动垃圾回收(最终)-因此不需要deleteLater()。一般来说,不要过分担心显式删除对象:注意“过早优化”!只有在您有“真实”的证据表明它们正在引起问题时才担心这些问题。 - ekhumoro
1
deleteLater()会导致事件循环在所有待处理事件传递给对象后删除该对象,因此通常比直接删除QObject子类更安全。我发现在PyQt/PySide中仍然有用。 - iMath

1

deleteLater的一个应用是清理自己,即安排删除QObject(例如在线程中),以便从对象本身释放资源。

例如,在此处示例中,有人将其与信号thread.finished连接在一起。但它可能仅限于信号传递较重的情况。


1
遗憾的是,示例链接现在已经失效了。(当然。当然它会失效。) - Cecil Curry
2
我更新了链接,粘贴了来自Web存档的链接。 - adamczi
这个链接是关于C++的。这个问题是关于Python和ARC的。 - ManuelSchneid3r

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