【问题标题】:PyQt4 setParent vs deleteLaterPyQt4 setParent vs deleteLater
【发布时间】:2015-07-26 07:49:56
【问题描述】:

我有一个布局,我添加了很多自定义小部件,例如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

我需要打电话给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 时,是否有一种“公认”的方式来进行清理?我应该使用setParentdeleteLater,还是两者都用?

【问题讨论】:

    标签: pyqt pyqt4


    【解决方案1】:

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

    >>> 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。从回溯中可以看出,这将删除底层的 C++ 对象,但它的 python 包装器对象将被留下。但是,一旦任何剩余的对它的 python 引用消失,垃圾收集器将(最终)删除这个包装器。请注意,在此过程中永远不需要调用setParent(None)

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

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

    【讨论】:

    • 那么setParent(None) 怎么样?它有什么缺点吗?
    • @Hzzkygcs 根据 OP 的分析,在某些情况下这可能是一项昂贵的操作。但正如我在回答中证明的那样,没有必要调用它,因为 Qt 在调用 deleteLater() 后会自动删除子引用。
    猜你喜欢
    • 1970-01-01
    • 2017-04-21
    • 2012-09-19
    • 2017-05-15
    • 2014-04-18
    • 1970-01-01
    • 2023-02-05
    • 2018-07-19
    • 1970-01-01
    相关资源
    最近更新 更多