It seems that a QGraphicsItem
status goes from To be destroyed by: C/C++
to To be destroyed by: Python
, while not destroyed and still accessible. Is it an expected behavior ? If so, can someone explain ?
node = QGraphicsRectItem()
self.scene.addItem(node)
print("Deleted ? ", sip.isdeleted(node))
print("Owned by Python ? ", sip.ispyowned(node))
sip.dump(node)
Output
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)
Output
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
One can see it is now owned by Python after deletion. And it still exists. Why ?
When you add the item to the scene, Qt takes ownership of it, so you don't need to keep an explicit reference to it on the Python side. This means that when the scene is deleted, the C++ part of the item will be deleted as well - and if there are no other references held to the Python part, it will also be garbage-collected (so there will be nothing left).
When you remove the item from the scene, ownership passes back to the caller, who now has full responsiblity for deleting it. If QGraphicsRectItem
inherited QObject
, you could call deleteLater()
to delete the C++ part - but this would still leave you with the empty Python wrapper, so it wouldn't really make much difference.
The way to delete a PyQt wrapper is the same as for any other python object - i.e. use del
, or simply let it go out of scope and allow it to be garbage-collected in the normal way. There is no special PyQt way to delete such objects, because nothing special is needed.
In your example, you never actually make any attempt to delete the item, so the information reported by sip is entirely as expected.
Finally, note that it is always possible to delete the C++ part independently of the PyQt wrapper. If you do this, and then attempt to access the item's methods, you will get a 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
UPDATE:
Below is a demo that shows how the item is garbage-collected when it is removed from the scene and allowed to go out of scope. Of course, if there are any references held to the item elsewhere (i.e. if ref-count > 1), the item will not be deleted.
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')
Output:
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