没有“包装器被重用”,只是旧的包装器对象(已删除)恰好位于新的包装器对象的相同内存地址。
PySide使用shiboken
库将QObject
封装为Python对象。 shiboken文档中提到:
与任何Python绑定一样,基于|project|的绑定使用引用计数来处理包装器对象(包含C ++对象的Python对象,不要将其与包装的C ++对象混淆)。当引用计数达到零时,“Python垃圾收集器会删除包装器并尝试删除封装实例,但有时封装的C ++对象已经被删除,或者可能C ++对象在Python包装器超出范围并死亡之后不应该被释放,因为C ++已经负责封装实例。”
除此之外,CPython malloc实现(以及许多其他常见的malloc)经常会
重复使用刚删除对象的内存地址分配给后续创建的对象,导致常见的陷阱:
>>> {} is {}
False
>>> id({}) == id({})
True
因此,活对象的地址/ID始终是不同的;曾经存在过但现在已死亡的对象的地址只是一个好奇心。
在第一种情况下,两个字典的引用都被保留,因此它们的ID在技术上是不同的,而在第二种情况下,左侧字典在左侧ID被调用后很快被删除,之后才创建右侧字典,并分配在完全相同的内存地址。
然而,这还有点更加复杂。当你调用
currentThread()
时,它会返回一个新的包装对象,用于该
QThread
,但
除非旧的包装对象仍然存活(即在Python端引用了它),否则将返回旧的包装对象:因此,
QtCore.QThread.currentThread() is QtCore.QThread.currentThread()
将始终为True!
现在,你得到2个地址的原因是
in thread <PySide.QtCore.QThread object at 0x00000000028EB888> message in main thread
in thread <PySide.QtCore.QThread object at 0x00000000028EB8C8> message in worker thread
再次强调,与包装器“未被重用”或被保留无关,同样的情况也可能发生在其他对象创建后,其引用被保留 - 也就是说,不能确定封装的对象是否一直存在。
然而,如果您保留对2个不同的QThread
对象的引用,则它们的id()
在其生命周期内将是不同的。
总的来说,你不应该依赖于shiboken包装对象的地址做任何有用的事情;相反,如果你在Python代码中自己持有引用,则
is
测试是有用的。
请注意,如果设置了
objectName
,
QObject
的
__str__
将打印它; 因此,您可以通过执行以下操作轻松理解输出:
app = QtCore.QCoreApplication([])
QtCore.QThread.currentThread().setObjectName('main thread')
thread = QtCore.QThread()
thread.start()
thread.setObjectName('worker thread')
并且使用更清晰的print
:
print('in thread: "{}" - message: "{}"'
.format(QtCore.QThread.currentThread().objectName(), text))
这些消息将是:
in thread: "main thread" - message: "in main thread"
in thread: "worker thread" - message: "in worker thread"
还有一种方法可以获取底层C++对象的地址,使用shiboken模块;这对于调试其他内容也很方便;不幸的是,默认情况下Ubuntu 14.10上未安装Python库或任何与shiboken相关的包;经过多次尝试,我最终设法手动安装它。
结果比我想象的更令人害怕:
app = QtCore.QCoreApplication([])
print(QtCore.QCoreApplication.instance().thread())
obj = QtCore.QObject()
print(obj.thread())
del obj
print(QtCore.QThread.currentThread())
打印:
<PySide.QtCore.QThread object at 0x7fb4a1149cc8>
<PySide.QtCore.QThread object at 0x7fb4a1149cc8>
<PySide.QtCore.QThread object at 0x7fb4a1149d08>
没错,当我删除QObject后,线程包装器的地址会发生变化!因此,让我们从Shiboken.shiboken中导入dump
:
from Shiboken.shiboken import dump
def info(obj):
print(id(obj))
print(dump(obj))
app = QtCore.QCoreApplication([])
info(QtCore.QCoreApplication.instance().thread())
obj = QtCore.QObject()
info(obj.thread())
del obj
info(QtCore.QCoreApplication.instance().thread())
输出结果为
140323585370568
C++ address....... PySide.QtCore.QThread/0xe3d880
hasOwnership...... 0
containsCppWrapper 0
validCppObject.... 1
wasCreatedByPython 0
parent............ <PySide.QtCore.QCoreApplication object at 0x7f9fa175a948>
140323585370568
C++ address....... PySide.QtCore.QThread/0xe3d880
hasOwnership...... 0
containsCppWrapper 0
validCppObject.... 1
wasCreatedByPython 0
parent............ <PySide.QtCore.QObject object at 0x7f9fa175aa48>
140323585370696
C++ address....... PySide.QtCore.QThread/0xe3d880
hasOwnership...... 0
containsCppWrapper 0
validCppObject.... 1
wasCreatedByPython 0
即,shiboken 使我们调用了
thread()
方法的最后一个对象成为我们线程的
父对象。每当
那个对象被删除(也就是调用了
.thread()
的最后一个对象),包装器也会被删除。这种行为非常可疑;我不确定它是否是一个错误,但这证明信任包装器对象的
id
是完全不可信的。
obj.thread()
后面加上del obj
会改变包装器的地址吗?我认为至少在我的版本中存在一个bug(把线程作为任何对象的子对象都没有意义)。 - Antti Haapala -- Слава Україні