QGraphicsItem 何时在 PyQt 中被销毁?

When does QGraphicsItem get destroyed in PyQt?

似乎 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。它仍然存在。为什么?

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

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