【问题标题】:When does QGraphicsItem get destroyed in PyQt?QGraphicsItem 什么时候在 PyQt 中被销毁?
【发布时间】:2018-07-18 01:13:31
【问题描述】:

似乎QGraphicsItem 状态从To be destroyed by: C/C++ 变为To be destroyed by: Python,虽然没有被破坏并且仍然可以访问。这是预期的行为吗?如果是这样,有人可以解释一下吗?

创作部分

node = QGraphicsRectItem()
self.scene.addItem(node)

print("Deleted ? ", sip.isdeleted(node))
print("Owned by Python ? ", sip.ispyowned(node))
sip.dump(node)

输出

Deleted ?  False
Owned by Python ?  False
<PyQt5.QtWidgets.QGraphicsRectItem object at 0x7fcdb82371f8>
    Reference count: 3
    Address of wrapped object: 0x214bf80
    Created by: Python
    To be destroyed by: C/C++
    Parent wrapper: <PyQt5.QtWidgets.QGraphicsScene object at 0x7fcdb821ea68>
    Next sibling wrapper: <__main__.Host object at 0x7fcdb8237dc8>
    Previous sibling wrapper: NULL
    First child wrapper: NULL

删除部分

self.scene.removeItem(node)

print("Deleted ? ", sip.isdeleted(node))
print("Owned by Python ? ", sip.ispyowned(node))
sip.dump(node)

输出

Deleted ?  False
Owned by Python ?  True
<PyQt5.QtWidgets.QGraphicsRectItem object at 0x7fcdb82371f8>
    Reference count: 2
    Address of wrapped object: 0x214bf80
    Created by: Python
    To be destroyed by: Python
    Parent wrapper: NULL
    Next sibling wrapper: NULL
    Previous sibling wrapper: NULL
    First child wrapper: NULL

可以看到删除后它现在归 Python 所有。它仍然存在。为什么?

【问题讨论】:

  • 您是否希望使用 removeItem () 将项目从内存中删除?如果是这样,removeItem() 不会从内存中删除该项目,其目标是在另一个场景中重用该项目,以便程序员负责管理该内存,这在 Qt 中也会发生。类似的行为有 QListWidgetItem。
  • @eyllanesc 同意。我不希望那样。关键是,使用PyQt,我没有显式析构函数或delete 指令,与C++ 一样。那我必须手动强制吗?如果是这样,最好的 PyQt'ish 方法是什么,而不是使用 sip 或 python 的其他解决方案(如del)?

标签: python pyqt pyqt5 destroy qgraphicsitem


【解决方案1】:

当您add the item to the scene, Qt takes ownership of it 时,您不需要在 Python 端保留对它的显式引用。这意味着当场景被删除时,项目的 C++ 部分也将被删除 - 如果没有其他对 Python 部分的引用,它也将被垃圾收集(因此不会留下任何东西)。

当您remove the item from the scene, ownership passes back to the caller 时,他现在全权负责删除它。如果QGraphicsRectItem 继承了QObject,你可以调用deleteLater() 来删除C++ 部分——但这仍然会给你留下空的Python 包装器,所以它不会有太大的区别。

删除 PyQt 包装器的方法与任何其他 python 对象相同 - 即使用 del,或者只是让它超出范围并允许以正常方式对其进行垃圾收集。没有特殊的 PyQt 方法可以删除这些对象,因为不需要什么特殊的。

在您的示例中,您实际上从未尝试删除该项目,因此 sip 报告的信息完全符合预期。

最后,请注意,始终可以独立于 PyQt 包装器删除 C++ 部分。如果你这样做,然后尝试访问项目的方法,你将得到一个RuntimeError

>>> scene = QGraphicsScene()
>>> node = QGraphicsRectItem()
>>> scene.addItem(node)
>>> del scene
>>> node.rect()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: wrapped C/C++ object of type QGraphicsRectItem has been deleted

更新

下面是一个演示,展示了当项目从场景中移除并允许超出范围时如何进行垃圾收集。当然,如果在其他地方有对该项目的任何引用(即如果 ref-count > 1),则不会删除该项目。

import sip, gc
from PyQt5 import QtCore, QtWidgets

app = QtWidgets.QApplication(['test'])

scene = QtWidgets.QGraphicsScene()

def test():
    node = QtWidgets.QGraphicsRectItem()
    scene.addItem(node)
    scene.removeItem(node)
    print("Deleted ? ", sip.isdeleted(node))
    print("Owned by Python ? ", sip.ispyowned(node))
    sip.dump(node)
    print()

test()

print('gc collect...')

gc.collect()

for obj in gc.get_objects():
    if isinstance(obj, QtWidgets.QGraphicsRectItem):
        print(obj)
        break
else:
    print('deleted')

输出:

Deleted ?  False
Owned by Python ?  True
<PyQt5.QtWidgets.QGraphicsRectItem object at 0x7f90f66d5798>
    Reference count: 3
    Address of wrapped object: 0x205bd80
    Created by: Python
    To be destroyed by: Python
    Parent wrapper: NULL
    Next sibling wrapper: NULL
    Previous sibling wrapper: NULL
    First child wrapper: NULL

gc collect...
deleted

【讨论】:

  • The ownership of item is passed on to the caller (i.e., QGraphicsScene will no longer delete item when destroyed). 现在说得通了,不是那样理解。此外,simply let it go out of scope and allow it to be garbage-collected in the normal way 从未见过收集的节点,即使在显式调用 gc.collect() 时也是如此。最后,您的建议是removeItem 然后让它被收集?因为,正如我所说,它似乎没有被收集
  • @JustinIurman。那一定是因为您在代码的其他地方保留了一些其他引用。我在我的答案中添加了一个演示,对我来说效果很好。
  • 谢谢,这就是问题所在。我在进行测试的函数内部持有一个本地引用。我的错。我现在得到了预期的行为。
【解决方案2】:

删除后不要让它超出范围。它已从 Qt 的所有内容中删除,但 Python 仍然具有您用来引用该项目的 node 变量。

【讨论】:

  • QGraphicsRectItem 不继承 QObject
  • 谢谢。我的错。已更新。
  • 很奇怪,因为node(继承自QObjectdeleteLater 也是如此)被很好地删除了。即使python仍然有参考。 deleteLater 背后的实现是什么?
猜你喜欢
  • 2023-03-31
  • 1970-01-01
  • 2016-05-25
  • 1970-01-01
  • 2019-12-09
  • 1970-01-01
  • 2023-03-21
  • 1970-01-01
  • 2011-05-26
相关资源
最近更新 更多