In short: using SIP Python API to transfer ownership to C++ while keeping the Python "part" of a QObject alive seemingly solves the issue, which is indeed an ownership one, as covered at (also see the comments in sources below):
On SIP API, see:
Also, some nice general advice is at:
A slightly modified example from the question, Python source:
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQuick import QQuickView
from PyQt5.QtCore import QUrl, QObject, QVariant, pyqtSlot
from PyQt5.QtQml import QQmlEngine, qmlRegisterType
import sip
def ownership(obj):
jsOwned = QQmlEngine.objectOwnership(obj) == QQmlEngine.JavaScriptOwnership
return "Javascript" if jsOwned else "Cpp"
def onDummytDestroyed():
print "Dummy destroyed"
class Dummy(QObject):
i = 0
def __init__(self):
#let's not forget to properly initialize the base part
super(Dummy, self).__init__()
self.i = Dummy.i
Dummy.i = Dummy.i + 1
def __del__(self):
#let's track when the Python part gets destroyed
# (or, rather, "finalized", or whatever the correct term is :) )
print "Dummy.__del__(), self.i:", self.i
@pyqtSlot(result="QString")
def get_str(self):
#let's have a Python (not C++) implemented method in the class
return "Dummy string #" + str(self.i)
class GUIEntryPoint(QObject):
def __init__(self):
#same as with Dummy.__init__
super(GUIEntryPoint, self).__init__()
@pyqtSlot(result=QVariant) #would also work
#@pyqtSlot(result=QObject)
def get_foo(self):
print "GUIEntryPoint.get_foo():"
foo = Dummy()
foo.setObjectName("Foo")
#let's track when the C++ part gets destroyed
foo.destroyed.connect(onDummytDestroyed)
#this would destroy the Python part of foo as soon as the reference
#goes out of scope (the C++ part stay alive and will be properly accessible from QML,
#but attempts to access Python-implemented methods like Dummy.get_str()
#would cause a TypeError hinting that there is no such method)
#sip.transferto(foo, None)
#this works as expected, with both Python (and then C++) parts of foo
#being destroyed whenever the QML GC decides to get rid of the object
#(see program output, notice how GC is silent for a while)
sip.transferto(foo, foo)
#this is indeed not needed, as proper ownership will be set
#when the object passes to the QML land through the slot invocation
#QQmlEngine.setObjectOwnership(foo, QQmlEngine.JavaScriptOwnership)
print " initial ownership: ", ownership(foo)
return foo
@pyqtSlot(QObject) #would also work
#@pyqtSlot(QVariant)
def print_foo(self, foo):
print "GUIEntryPoint.print_foo():"
print " objectName: ", foo.objectName()
print " ownership when on QML side: ", ownership(foo)
app = QGuiApplication([])
view = QQuickView()
#not needed, indeed, as both context objects (and properties),
#and QObject descendants passed to QML side are perfectly accessible
#(that is, their properties and invokables / slots are)
#without any additional steps
#(instantiating types declaratively would require registering them first, though,
#and having properties of custom types requires metatypes to be declared)
#qmlRegisterType(GUIEntryPoint, 'GUIEntryPoint', 1, 0, 'GUIEntryPoint')
#qmlRegisterType(Dummy, 'Dummy', 1, 0, 'Dummy')
gep = GUIEntryPoint()
view.rootContext().setContextProperty('BE', gep)
view.setSource(QUrl.fromLocalFile("pyqt-ownership-test.qml"))
view.show()
app.exec_()
QML source (save as pyqt-ownership-test.py):
import QtQuick 2.1
Rectangle {
width: 300
height: 200
color: "red"
border.width: 10
Component.onCompleted: {
console.log("Component.onCompleted():")
//increase the number to 1000 to see GC stepping in
//while the loop is still running
//(notice how destruction is not being reported for some objects,
//for example, #249 (the one created last),
//but this looks like a Qt/QML logging issue)
for (var i = 0; i < 250; i++) {
console.log("#" + i)
var foo = BE.get_foo()
console.log(" foo: " + foo)
console.log(" foo.objectName: " + foo.objectName)
console.log(" foo.get_str(): " + foo.get_str())
console.log(" print_foo():")
BE.print_foo(foo)
}
}
}
Program output (stripped in the middle to accomodate SO answer size limit):
Component.onCompleted():
#0
GUIEntryPoint.get_foo():
initial ownership: Cpp
foo: Dummy(0x22903d0, "Foo")
foo.objectName: Foo
foo.get_str(): Dummy string #0
print_foo():
GUIEntryPoint.print_foo():
objectName: Foo
ownership when on QML side: Javascript
#1
GUIEntryPoint.get_foo():
initial ownership: Cpp
foo: Dummy(0x2290d20, "Foo")
foo.objectName: Foo
foo.get_str(): Dummy string #1
print_foo():
GUIEntryPoint.print_foo():
objectName: Foo
ownership when on QML side: Javascript
#2
GUIEntryPoint.get_foo():
initial ownership: Cpp
foo: Dummy(0x2291080, "Foo")
foo.objectName: Foo
foo.get_str(): Dummy string #2
print_foo():
GUIEntryPoint.print_foo():
objectName: Foo
ownership when on QML side: Javascript
#3
GUIEntryPoint.get_foo():
initial ownership: Cpp
foo: Dummy(0x22911e0, "Foo")
foo.objectName: Foo
foo.get_str(): Dummy string #3
print_foo():
GUIEntryPoint.print_foo():
objectName: Foo
ownership when on QML side: Javascript
#4
GUIEntryPoint.get_foo():
initial ownership: Cpp
foo: Dummy(0x2291e90, "Foo")
foo.objectName: Foo
foo.get_str(): Dummy string #4
print_foo():
GUIEntryPoint.print_foo():
objectName: Foo
ownership when on QML side: Javascript
#5
GUIEntryPoint.get_foo():
initial ownership: Cpp
foo: Dummy(0x2292b70, "Foo")
foo.objectName: Foo
foo.get_str(): Dummy string #5
print_foo():
GUIEntryPoint.print_foo():
objectName: Foo
ownership when on QML side: Javascript
#6
GUIEntryPoint.get_foo():
initial ownership: Cpp
foo: Dummy(0x2293420, "Foo")
foo.objectName: Foo
foo.get_str(): Dummy string #6
print_foo():
GUIEntryPoint.print_foo():
objectName: Foo
ownership when on QML side: Javascript
#7
GUIEntryPoint.get_foo():
initial ownership: Cpp
foo: Dummy(0x2296e30, "Foo")
foo.objectName: Foo
foo.get_str(): Dummy string #7
print_foo():
GUIEntryPoint.print_foo():
objectName: Foo
ownership when on QML side: Javascript
#8
GUIEntryPoint.get_foo():
initial ownership: Cpp
foo: Dummy(0x22983b0, "Foo")
foo.objectName: Foo
foo.get_str(): Dummy string #8
print_foo():
GUIEntryPoint.print_foo():
objectName: Foo
ownership when on QML side: Javascript
#9
GUIEntryPoint.get_foo():
initial ownership: Cpp
foo: Dummy(0x2299150, "Foo")
foo.objectName: Foo
foo.get_str(): Dummy string #9
print_foo():
GUIEntryPoint.print_foo():
objectName: Foo
ownership when on QML side: Javascript
#10
GUIEntryPoint.get_foo():
initial ownership: Cpp
foo: Dummy(0x2299e60, "Foo")
foo.objectName: Foo
foo.get_str(): Dummy string #10
print_foo():
GUIEntryPoint.print_foo():
objectName: Foo
ownership when on QML side: Javascript
#11
GUIEntryPoint.get_foo():
initial ownership: Cpp
foo: Dummy(0x229ab70, "Foo")
foo.objectName: Foo
foo.get_str(): Dummy string #11
print_foo():
GUIEntryPoint.print_foo():
objectName: Foo
ownership when on QML side: Javascript
<...some lines skipped here...>
#245
GUIEntryPoint.get_foo():
initial ownership: Cpp
foo: Dummy(0x23007b0, "Foo")
foo.objectName: Foo
foo.get_str(): Dummy string #245
print_foo():
GUIEntryPoint.print_foo():
objectName: Foo
ownership when on QML side: Javascript
#246
GUIEntryPoint.get_foo():
initial ownership: Cpp
foo: Dummy(0x22fe4d0, "Foo")
foo.objectName: Foo
foo.get_str(): Dummy string #246
print_foo():
GUIEntryPoint.print_foo():
objectName: Foo
ownership when on QML side: Javascript
#247
GUIEntryPoint.get_foo():
initial ownership: Cpp
foo: Dummy(0x22fc5c0, "Foo")
foo.objectName: Foo
foo.get_str(): Dummy string #247
print_foo():
GUIEntryPoint.print_foo():
objectName: Foo
ownership when on QML side: Javascript
#248
GUIEntryPoint.get_foo():
initial ownership: Cpp
foo: Dummy(0x22fa730, "Foo")
foo.objectName: Foo
foo.get_str(): Dummy string #248
print_foo():
GUIEntryPoint.print_foo():
objectName: Foo
ownership when on QML side: Javascript
#249
GUIEntryPoint.get_foo():
initial ownership: Cpp
foo: Dummy(0x22f57b0, "Foo")
foo.objectName: Foo
foo.get_str(): Dummy string #249
print_foo():
GUIEntryPoint.print_foo():
objectName: Foo
ownership when on QML side: Javascript
Dummy.__del__(), self.i: 214
Dummy destroyed
Dummy.__del__(), self.i: 213
Dummy destroyed
Dummy.__del__(), self.i: 212
Dummy destroyed
Dummy.__del__(), self.i: 211
Dummy destroyed
Dummy.__del__(), self.i: 210
Dummy destroyed
Dummy.__del__(), self.i: 209
Dummy destroyed
Dummy.__del__(), self.i: 208
Dummy destroyed
Dummy.__del__(), self.i: 207
Dummy destroyed
Dummy.__del__(), self.i: 206
Dummy destroyed
Dummy.__del__(), self.i: 205
Dummy destroyed
Dummy.__del__(), self.i: 204
Dummy destroyed
Dummy.__del__(), self.i: 203
Dummy destroyed
Dummy.__del__(), self.i: 202
Dummy destroyed
Dummy.__del__(), self.i: 201
Dummy destroyed
Dummy.__del__(), self.i: 200
Dummy destroyed
Dummy.__del__(), self.i: 199
Dummy destroyed
Dummy.__del__(), self.i: 198
Dummy destroyed
Dummy.__del__(), self.i: 197
Dummy destroyed
Dummy.__del__(), self.i: 196
Dummy destroyed
Dummy.__del__(), self.i: 195
Dummy destroyed
Dummy.__del__(), self.i: 194
Dummy destroyed
Dummy.__del__(), self.i: 193
Dummy destroyed
Dummy.__del__(), self.i: 192
Dummy destroyed
Dummy.__del__(), self.i: 191
Dummy destroyed
Dummy.__del__(), self.i: 190
Dummy destroyed
Dummy.__del__(), self.i: 189
Dummy destroyed
Dummy.__del__(), self.i: 188
Dummy destroyed
Dummy.__del__(), self.i: 187
Dummy destroyed
Dummy.__del__(), self.i: 186
Dummy destroyed
Dummy.__del__(), self.i: 185
Dummy destroyed
Dummy.__del__(), self.i: 184
Dummy destroyed
Dummy.__del__(), self.i: 183
Dummy destroyed
Dummy.__del__(), self.i: 182
Dummy destroyed
Dummy.__del__(), self.i: 181
Dummy destroyed
Dummy.__del__(), self.i: 180
Dummy destroyed
Dummy.__del__(), self.i: 179
Dummy destroyed
Dummy.__del__(), self.i: 178
Dummy destroyed
Dummy.__del__(), self.i: 177
Dummy destroyed
Dummy.__del__(), self.i: 176
Dummy destroyed
Dummy.__del__(), self.i: 175
Dummy destroyed
Dummy.__del__(), self.i: 174
Dummy destroyed
Dummy.__del__(), self.i: 173
Dummy destroyed
Dummy.__del__(), self.i: 172
Dummy destroyed
Dummy.__del__(), self.i: 171
Dummy destroyed
Dummy.__del__(), self.i: 170
Dummy destroyed
Dummy.__del__(), self.i: 169
Dummy destroyed
Dummy.__del__(), self.i: 168
Dummy destroyed
Dummy.__del__(), self.i: 167
Dummy destroyed
Dummy.__del__(), self.i: 166
Dummy destroyed
Dummy.__del__(), self.i: 165
Dummy destroyed
Dummy.__del__(), self.i: 164
Dummy destroyed
Dummy.__del__(), self.i: 163
Dummy destroyed
Dummy.__del__(), self.i: 162
Dummy destroyed
Dummy.__del__(), self.i: 161
Dummy destroyed
Dummy.__del__(), self.i: 160
Dummy destroyed
Dummy.__del__(), self.i: 159
Dummy destroyed
Dummy.__del__(), self.i: 158
Dummy destroyed
Dummy.__del__(), self.i: 157
Dummy destroyed
Dummy.__del__(), self.i: 156
Dummy destroyed
Dummy.__del__(), self.i: 155
Dummy destroyed
Dummy.__del__(), self.i: 154
Dummy destroyed
Dummy.__del__(), self.i: 153
Dummy destroyed
Dummy.__del__(), self.i: 152
Dummy destroyed
Dummy.__del__(), self.i: 151
Dummy destroyed
Dummy.__del__(), self.i: 150
Dummy destroyed
Dummy.__del__(), self.i: 149
Dummy destroyed
Dummy.__del__(), self.i: 148
Dummy destroyed
Dummy.__del__(), self.i: 147
Dummy destroyed
Dummy.__del__(), self.i: 146
Dummy destroyed
Dummy.__del__(), self.i: 145
Dummy destroyed
Dummy.__del__(), self.i: 144
Dummy destroyed
Dummy.__del__(), self.i: 143
Dummy destroyed
Dummy.__del__(), self.i: 142
Dummy destroyed
Dummy.__del__(), self.i: 141
Dummy destroyed
Dummy.__del__(), self.i: 140
Dummy destroyed
Dummy.__del__(), self.i: 139
Dummy destroyed
Dummy.__del__(), self.i: 138
Dummy destroyed
Dummy.__del__(), self.i: 137
Dummy destroyed
Dummy.__del__(), self.i: 136
Dummy destroyed
Dummy.__del__(), self.i: 135
Dummy destroyed
Dummy.__del__(), self.i: 134
Dummy destroyed
Dummy.__del__(), self.i: 133
Dummy destroyed
Dummy.__del__(), self.i: 132
Dummy destroyed
Dummy.__del__(), self.i: 131
Dummy destroyed
Dummy.__del__(), self.i: 130
Dummy destroyed
Dummy.__del__(), self.i: 129
Dummy destroyed
Dummy.__del__(), self.i: 128
Dummy destroyed
Dummy.__del__(), self.i: 127
Dummy destroyed
Dummy.__del__(), self.i: 126
Dummy destroyed
Dummy.__del__(), self.i: 125
Dummy destroyed
Dummy.__del__(), self.i: 124
Dummy destroyed
Dummy.__del__(), self.i: 123
Dummy destroyed
Dummy.__del__(), self.i: 122
Dummy destroyed
Dummy.__del__(), self.i: 121
Dummy destroyed
Dummy.__del__(), self.i: 120
Dummy destroyed
Dummy.__del__(), self.i: 119
Dummy destroyed
Dummy.__del__(), self.i: 118
Dummy destroyed
Dummy.__del__(), self.i: 117
Dummy destroyed
Dummy.__del__(), self.i: 116
Dummy destroyed
Dummy.__del__(), self.i: 115
Dummy destroyed
Dummy.__del__(), self.i: 114
Dummy destroyed
Dummy.__del__(), self.i: 113
Dummy destroyed
Dummy.__del__(), self.i: 112
Dummy destroyed
Dummy.__del__(), self.i: 111
Dummy destroyed
Dummy.__del__(), self.i: 110
Dummy destroyed
Dummy.__del__(), self.i: 109
Dummy destroyed
Dummy.__del__(), self.i: 108
Dummy destroyed
Dummy.__del__(), self.i: 107
Dummy destroyed
Dummy.__del__(), self.i: 106
Dummy destroyed
Dummy.__del__(), self.i: 105
Dummy destroyed
Dummy.__del__(), self.i: 104
Dummy destroyed
Dummy.__del__(), self.i: 103
Dummy destroyed
Dummy.__del__(), self.i: 102
Dummy destroyed
Dummy.__del__(), self.i: 101
Dummy destroyed
Dummy.__del__(), self.i: 100
Dummy destroyed
Dummy.__del__(), self.i: 99
Dummy destroyed
Dummy.__del__(), self.i: 98
Dummy destroyed
Dummy.__del__(), self.i: 97
Dummy destroyed
Dummy.__del__(), self.i: 96
Dummy destroyed
Dummy.__del__(), self.i: 95
Dummy destroyed
Dummy.__del__(), self.i: 94
Dummy destroyed
Dummy.__del__(), self.i: 93
Dummy destroyed
Dummy.__del__(), self.i: 92
Dummy destroyed
Dummy.__del__(), self.i: 91
Dummy destroyed
Dummy.__del__(), self.i: 90
Dummy destroyed
Dummy.__del__(), self.i: 89
Dummy destroyed
Dummy.__del__(), self.i: 88
Dummy destroyed
Dummy.__del__(), self.i: 87
Dummy destroyed
Dummy.__del__(), self.i: 86
Dummy destroyed
Dummy.__del__(), self.i: 85
Dummy destroyed
Dummy.__del__(), self.i: 84
Dummy destroyed
Dummy.__del__(), self.i: 83
Dummy destroyed
Dummy.__del__(), self.i: 82
Dummy destroyed
Dummy.__del__(), self.i: 81
Dummy destroyed
Dummy.__del__(), self.i: 80
Dummy destroyed
Dummy.__del__(), self.i: 79
Dummy destroyed
Dummy.__del__(), self.i: 78
Dummy destroyed
Dummy.__del__(), self.i: 77
Dummy destroyed
Dummy.__del__(), self.i: 76
Dummy destroyed
Dummy.__del__(), self.i: 75
Dummy destroyed
Dummy.__del__(), self.i: 74
Dummy destroyed
Dummy.__del__(), self.i: 73
Dummy destroyed
Dummy.__del__(), self.i: 72
Dummy destroyed
Dummy.__del__(), self.i: 71
Dummy destroyed
Dummy.__del__(), self.i: 70
Dummy destroyed
Dummy.__del__(), self.i: 69
Dummy destroyed
Dummy.__del__(), self.i: 68
Dummy destroyed
Dummy.__del__(), self.i: 67
Dummy destroyed
Dummy.__del__(), self.i: 66
Dummy destroyed
Dummy.__del__(), self.i: 65
Dummy destroyed
Dummy.__del__(), self.i: 64
Dummy destroyed
Dummy.__del__(), self.i: 63
Dummy destroyed
Dummy.__del__(), self.i: 62
Dummy destroyed
Dummy.__del__(), self.i: 61
Dummy destroyed
Dummy.__del__(), self.i: 60
Dummy destroyed
Dummy.__del__(), self.i: 59
Dummy destroyed
Dummy.__del__(), self.i: 58
Dummy destroyed
Dummy.__del__(), self.i: 57
Dummy destroyed
Dummy.__del__(), self.i: 56
Dummy destroyed
Dummy.__del__(), self.i: 55
Dummy destroyed
Dummy.__del__(), self.i: 54
Dummy destroyed
Dummy.__del__(), self.i: 53
Dummy destroyed
Dummy.__del__(), self.i: 52
Dummy destroyed
Dummy.__del__(), self.i: 51
Dummy destroyed
Dummy.__del__(), self.i: 50
Dummy destroyed
Dummy.__del__(), self.i: 49
Dummy destroyed
Dummy.__del__(), self.i: 48
Dummy destroyed
Dummy.__del__(), self.i: 47
Dummy destroyed
Dummy.__del__(), self.i: 46
Dummy destroyed
Dummy.__del__(), self.i: 45
Dummy destroyed
Dummy.__del__(), self.i: 44
Dummy destroyed
Dummy.__del__(), self.i: 43
Dummy destroyed
Dummy.__del__(), self.i: 42
Dummy destroyed
Dummy.__del__(), self.i: 41
Dummy destroyed
Dummy.__del__(), self.i: 40
Dummy destroyed
Dummy.__del__(), self.i: 39
Dummy destroyed
Dummy.__del__(), self.i: 38
Dummy destroyed
Dummy.__del__(), self.i: 37
Dummy destroyed
Dummy.__del__(), self.i: 36
Dummy destroyed
Dummy.__del__(), self.i: 35
Dummy destroyed
Dummy.__del__(), self.i: 34
Dummy destroyed
Dummy.__del__(), self.i: 33
Dummy destroyed
Dummy.__del__(), self.i: 32
Dummy destroyed
Dummy.__del__(), self.i: 31
Dummy destroyed
Dummy.__del__(), self.i: 30
Dummy destroyed
Dummy.__del__(), self.i: 29
Dummy destroyed
Dummy.__del__(), self.i: 28
Dummy destroyed
Dummy.__del__(), self.i: 27
Dummy destroyed
Dummy.__del__(), self.i: 26
Dummy destroyed
Dummy.__del__(), self.i: 25
Dummy destroyed
Dummy.__del__(), self.i: 24
Dummy destroyed
Dummy.__del__(), self.i: 23
Dummy destroyed
Dummy.__del__(), self.i: 22
Dummy destroyed
Dummy.__del__(), self.i: 21
Dummy destroyed
Dummy.__del__(), self.i: 20
Dummy destroyed
Dummy.__del__(), self.i: 19
Dummy destroyed
Dummy.__del__(), self.i: 18
Dummy destroyed
Dummy.__del__(), self.i: 17
Dummy destroyed
Dummy.__del__(), self.i: 16
Dummy destroyed
Dummy.__del__(), self.i: 15
Dummy destroyed
Dummy.__del__(), self.i: 14
Dummy destroyed
Dummy.__del__(), self.i: 13
Dummy destroyed
Dummy.__del__(), self.i: 12
Dummy destroyed
Dummy.__del__(), self.i: 11
Dummy destroyed
Dummy.__del__(), self.i: 10
Dummy destroyed
Dummy.__del__(), self.i: 9
Dummy destroyed
Dummy.__del__(), self.i: 8
Dummy destroyed
Dummy.__del__(), self.i: 7
Dummy destroyed
Dummy.__del__(), self.i: 6
Dummy destroyed
Dummy.__del__(), self.i: 5
Dummy destroyed
Dummy.__del__(), self.i: 4
Dummy destroyed
Dummy.__del__(), self.i: 3
Dummy destroyed
Dummy.__del__(), self.i: 2
Dummy destroyed
Dummy.__del__(), self.i: 1
Dummy destroyed
Dummy.__del__(), self.i: 0
Dummy destroyed