PyQt4 setParent vs deleteLater

时间:2015-05-14 15:50:20

标签: pyqt pyqt4

我有一个布局,我添加了许多自定义小部件,如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的引用发生了什么。

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

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

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

我是否需要致电setParentdeleteLaterdeleteLater是否足以正确清理它?

作为旁注,我发现在这种情况下调用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时,有一种“接受”的方式来进行清理吗?我应该使用setParentdeleteLater还是两者兼而有之?

1 个答案:

答案 0 :(得分:8)

看看实际发生的事情的最简单方法可能是在互动环节中逐步完成:

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

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

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

由于布局和它们包含的小部件之间没有父/子关系,因此访问底层对象需要不同的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。从traceback中可以看出,这将删除底层的C ++对象,但它的python包装器对象将被遗忘。但是,一旦任何剩余的python引用消失,这个包装器(最终)将被垃圾收集器删除。请注意,在此过程中永远不需要调用setParent(None)

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

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