PyQt4 setParent vs deleteLater [英] PyQt4 setParent vs deleteLater

查看:33
本文介绍了PyQt4 setParent vs deleteLater的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

def _removeFilterWidgetFromLayout(self, widget):"""删除过滤器小部件"""self._collection_layout.removeWidget(widget)widget.setParent(无)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章2168 def _removeFilterWidgetFromLayout(自我,小部件):2169 """删除过滤器小部件"""21702171 233 1528 6.6 1.0 self._collection_layout.removeWidget(小部件)2172 233 143998 618.0 97.9 widget.setParent(无)2173 233 1307 5.6 0.9 widget.deleteLater()

当使用 PyQt4 时,是否有一种可接受的"方式来进行这种清理?我应该使用 setParentdeleteLater 还是两者都使用?

解决方案

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

<预><代码>>>>parent = QtGui.QWidget()>>>child = QtGui.QWidget()>>>布局 = QtGui.QHBoxLayout(parent)>>>layout.addWidget(child)>>>child.parent() 是布局错误的>>>child.parent() 是父母真的

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

<预><代码>>>>item = layout.itemAt(0)>>>物品<PyQt4.QtGui.QWidgetItem 对象在 0x7fa1715fe318>>>>item.widget() 是孩子真的

由于布局和它们包含的小部件之间没有父/子关系,因此需要不同的 API 来访问底层对象.项目归布局所有,但底层对象的所有权保持不变.

<预><代码>>>>layout.removeWidget(child)>>>child.parent() 是父母真的>>>布局.计数()0>>>repr(layout.itemAt(0))'没有任何'>>>物品<PyQt4.QtGui.QWidgetItem 对象在 0x7fa1715fe318>

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

<预><代码>>>>child.deleteLater()>>>父母孩子()[]>>>child.parent()回溯(最近一次调用最后一次):文件<stdin>",第 1 行,在 <module> 中运行时错误:QWidget 类型的包装 C/C++ 对象已被删除>>>

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

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

<预><代码>>>>进口 sip>>>sip.delete(child)

I have a layout that I add a lot of custom widgets to with something like layout.addWidget(widget). Later I want to remove all those custom widgets and add new ones. I'm confused on the best way to do this when it comes to deleteLater and setParent(None). For example, here's my cleanup function that's called for all the widgets in the layout:

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

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

I have a suspicion that not all of these lines are needed to properly cleanup the memory used by the widget. I'm unsure of what is happening with the C++ and Python references to widget.

Here's what I understand about the C++ and Python references to QObjects in PyQt.

I believe that when you add a widget to a layout the widget becomes a child of the layout. So, if I call removeWidget then the parent relationship is broken so I have to clean up the C++ and Python reference myself since the widget has no other parent. The call to setParent is an explicit way of removing the parent relationship with the layout. The call to deleteLater is meant to take care of the C++ reference.

The Python reference is garbage collected because the widget variable goes out of scope, and there are no other Python objects pointing to widget.

Do I need to call setParent and deleteLater or would deleteLater be enough to properly clean this up?

As a side note, I've found that calling setParent(None) is a very expensive function call in this scenario. I can greatly speed up my entire cleanup process by removing this call. I'm not sure if deleteLater is enough to clean everything up correctly. Here's my profiling output from 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()

When using PyQt4 is there an 'accepted' way to do this cleanup? Should I use setParent, deleteLater, or both?

解决方案

Probably the easiest way to see what's actually going on is to step through things in an interactive session:

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

So the layout does not become the parent of the widget. This makes sense, because widgets can only have other widgets as parents, and layouts are not widgets. All widgets added to a layout will eventually have their parents reset to the parent of the layout (whenever it gets one).

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

Since there is no parent/child relationship bewteen layouts and the widgets they contain, a different API is needed for access to the underlying objects. The items are owned by the layout, but the ownership of the underlying objects remains unchanged.

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

At this point, the layout has deleted its item (because it had ownership of it), and thus no longer holds any references to the contained widget. Given this, it is no longer safe to do much with the python wrapper for the item (the interpreter would probably crash if we tried to call any of its methods).

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

Since we still have ownership of the child widget, we can call deleteLater on it. And as can be seen from the traceback, this will delete the underlying C++ object, but its python wrapper object will be left behind. However, this wrapper will (eventually) be deleted by the garbage collector, once any remaining python references to it are gone. Note that there is never any need to call setParent(None) during this process.

One final point: the above interpreter session is slightly misleading, because the event-queue is processed every time a line is executed. This means the effects of deleteLater are seen immediately, which would not be the case if the code was run as a script. To get immediate deletion in a script, you would need to use the sip module:

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

这篇关于PyQt4 setParent vs deleteLater的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆