[PyQt] Re: SIP bug with object lifetime

Giovanni Bajo rasky at develer.com
Mon May 14 09:33:41 BST 2007

On 14/05/2007 9.33, Phil Thompson wrote:

>>>> =========================================
>>>> from qt import *
>>>> import weakref
>>>> app = QApplication([])
>>>> ql = QListView(None)
>>>> viewport = ql.viewport()
>>>> o = QObject(viewport)
>>>> o.xxx = viewport  # bug-trigger!
>>>> destroyed = []
>>>> def cb(wr):
>>>>      destroyed.append(1)
>>>> wr = weakref.ref(o, cb)
>>>> del o
>>>> del viewport
>>>> assert not destroyed, "object destroyed too early #1!"
>>>> import gc
>>>> gc.collect()
>>>> assert not destroyed, "object destroyed too early #2!"
>>>> del ql
>>>> import gc
>>>> gc.collect()
>>>> assert destroyed, "object never destroyed!"
>>>> =========================================
>>>> Traceback (most recent call last):
>>>>    File "pyqtbug19.py", line 25, in ?
>>>>      assert not destroyed, "object destroyed too early #2!"
>>>> AssertionError: object destroyed too early #2!
>>>> This happens with latest PyQt and SIP official releases (3.17.1 and
>>>> 4.6). The line that seems to trigger the bug is the one marked with a
>>>> comment.
>>> This behaves as I would expect - ie. it's a missing feature rather than a
>>> bug.
>>> Although ql is the parent of viewport, Python doesn't know that and there
>>> is no hidden extra reference to viewport to keep it alive when collect()
>>> is run.
>> But the problem here is that "o" is collected. o is a QObject whose
>> lifetime should be transferred to C++ (given that it has a non-NULL parent,
>> right?).
> But that parent is owned by Python. When viewpoint (Python) goes, viewpoint 
> (C++) goes, which takes o (C++), which takes o (Python).

Are you saying that "viewport" is owned by Python?

If that was the case, it would be almost impossible to use the .viewport() 
method without causing damage (you would have to store a reference to the 
Python object). I'm pretty sure that can't be the case... I verified with this 

from qt import *
import weakref

app = QApplication([])

ql = QListView(None)
viewport = ql.viewport()

def cbpy(wr):
     print "Python destroyed!"

def cbcxx():
     print "C++ destroyed!"

QObject.connect(viewport, SIGNAL("destroyed()"), cbcxx)
wr = weakref.ref(viewport, cbpy)

if 0:
     import sip

del viewport
print "exiting"

Without the call to transferback, I get:

    Python destroyed!
    C++ destroyed!

which means that viewport is owned by C++, since the Python reference didn't 
bring down the C++ reference. If I change the ownership to Python using the 
explicit transferback, I get this:

    Python destroyed!
    C++ destroyed!

which looks correct, since the C++ code probably crashes as soon as the first 
messages are dispatched, because it expects the viewport to be alive.

(PS: what about a debugging function in sip which tells who owns who? 
sip.owner(foo) which returns a sip.voidptr() to the owner if it's C++, or None 
if it's owned by Python. It would make debugging of situations like this a 
little easier... using weakref/SIGNAL(destroyed) can bring other bugs into the 
table and makes things more confusing)

>> And why is this behaviour triggered *only* when I add the "xxx" attribute
>> to o? If I don't do that, everything looks right.
> That confuses me a little bit too. It may be a case of timing - objects are 
> not guaranteed to be garbage collected immediately their reference count 
> reaches 0.

I'll note that, without the "xxx" attribute, the whole example works as 
expected: even after the explicit gc.collect() call (which is guaranteed to 
collect *everything*), neither "o" nor "viewport" are collected, which is what 
I was expecting in the first place.
Giovanni Bajo

More information about the PyQt mailing list