PyQt4 setParent 与 deleteLater

PyQt4 setParent vs deleteLater

我有一个布局,我用 layout.addWidget(widget) 之类的东西添加了很多自定义小部件。稍后我想删除所有这些自定义小部件并添加新的。当涉及到 deleteLatersetParent(None) 时,我对执行此操作的最佳方法感到困惑。例如,这是为布局中的所有小部件调用的清理函数:

def _removeFilterWidgetFromLayout(self, widget):
    """Remove filter widget"""

    self._collection_layout.removeWidget(widget)
    widget.setParent(None)
    widget.deleteLater()

我怀疑并非所有这些行都需要正确清理小部件使用的内存。我不确定 C++ 和 Python 对 widget.

的引用发生了什么

以下是我对 C++ 的理解以及 Python 对 PyQt 中 QObject 的引用。

我相信当您将小部件添加到布局时,小部件将成为布局的子项。所以,如果我调用 removeWidget 那么父关系就被打破了,所以我必须自己清理 C++ 和 Python 引用,因为小部件没有其他父对象。对 setParent 的调用是一种删除与布局的父关系的显式方法。调用 deleteLater 是为了处理 C++ 引用。

Python 引用被垃圾回收,因为 widget 变量超出范围,并且没有其他 Python 对象指向 widget

我需要打电话给 setParentdeleteLater 或者 deleteLater 是否足以正确清理它?

作为旁注,我发现在这种情况下调用 setParent(None) 是一个非常昂贵的函数调用。通过删除此调用,我可以大大加快整个清理过程。我不确定 deleteLater 是否足以正确清理所有内容。这是 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()

使用 PyQt4 时,是否有 'accepted' 方法来进行此清理?我应该使用 setParentdeleteLater 还是两者都使用?

查看实际情况的最简单方法可能是在交互式会话中逐步完成:

>>> parent = QtGui.QWidget()
>>> child = QtGui.QWidget()
>>> layout = QtGui.QHBoxLayout(parent)
>>> layout.addWidget(child)
>>> child.parent() is layout
False
>>> child.parent() is parent
True

因此布局不会成为小部件的父级。这是有道理的,因为小部件只能有 other widgets 作为父级,而布局不是小部件。所有添加到布局的小部件最终都会将其父级重置为布局的父级(无论何时)。

>>> item = layout.itemAt(0)
>>> item
<PyQt4.QtGui.QWidgetItem object at 0x7fa1715fe318>
>>> item.widget() is child
True

由于布局与其包含的小部件之间没有 parent/child 关系,因此需要不同的 API 来访问底层对象。这些项目归布局所有,但底层对象的所有权保持不变。

>>> layout.removeWidget(child)
>>> child.parent() is parent
True
>>> layout.count()
0
>>> repr(layout.itemAt(0))
'None'
>>> item
<PyQt4.QtGui.QWidgetItem object at 0x7fa1715fe318>

此时,布局已删除其项目(因为它拥有它的所有权),因此不再持有对包含的小部件的任何引用。鉴于此,对项目的 python 包装器做很多事情不再安全(如果我们尝试调用它的任何方法,解释器可能会崩溃)。

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

由于我们仍然拥有子小部件的所有权,因此我们可以对其调用 deleteLater。从回溯中可以看出,这将删除底层的 C++ 对象,但它的 python 包装器对象将被留下。然而,一旦对它的任何剩余 python 引用消失,这个包装器将(最终)被垃圾收集器删除。请注意,在此过程中永远不需要调用 setParent(None)

最后一点:上面的解释器会话有点误导,因为每次执行一行时都会处理事件队列。这意味着立即可以看到 deleteLater 的效果,如果代码是 运行 作为脚本,情况就不会如此。要在脚本中立即删除,您需要使用 sip 模块:

>>> import sip
>>> sip.delete(child)