[PyQt] Weird behaviour of destroyed() slots

Phil Thompson phil at riverbankcomputing.com
Mon Jan 26 10:30:58 GMT 2009


On Thu, 22 Jan 2009 15:56:28 +0100, Lorenzo Mancini <lmancini at develer.com>
wrote:
> Phil Thompson wrote:
>> On Sat, 26 Jul 2008 21:07:09 +0200, Giovanni Bajo <rasky at develer.com>
>> wrote:
>>> On Sat, 2008-07-26 at 17:10 +0100, Phil Thompson wrote:
>>>> On Wed, 23 Jul 2008 15:42:18 +0200, Giovanni Bajo <rasky at develer.com>
>>>> wrote:
>>>>> Hi Phil:
>>>>>
>>>>>
>>
=============================================================================
>>>>> import sip
>>>>> from PyQt4.Qt import *
>>>>>
>>>>> called = []
>>>>>
>>>>> class Core(QObject):
>>>>>      def __init__(self, parent=None):
>>>>>          QWidget.__init__(self, parent)
>>>>>          QObject.connect(self, SIGNAL("destroyed()"), self.callback)
>>>>>          QObject.connect(self, SIGNAL("destroyed()"), lambda: 
>>>>> self.callback())
>>>>>      def callback(self):
>>>>>          called.append("done")
>>>>>
>>>>> app = QApplication([])
>>>>> core = Core(app)
>>>>> sip.delete(core)
>>>>>
>>>>> assert len(called) == 2, called
>>>>>
>>
=============================================================================
>>>>> Traceback (most recent call last):
>>>>>    File "bugpyqt.py", line 18, in <module>
>>>>>      assert len(called) == 2, called
>>>>> AssertionError: ['done']
>>>>>
>>>>> The slot with "lambda" is called, but the other one is not.
>>>> The reason is that PyQt knows that self is being destroyed and
>>>> self.callback may be a wrapped C++ method (although it isn't in this
>>> case)
>>>> so it won't invoke it.
>>> Why does it refuse to invoke a wrapped C++ method? Even if self is
being
>>> destroyed (that is: within its C++ destructor) is still valid to invoke
>>> methods AFAICT. After all, it's something that's perfectly doable in
C++
>>> as well. Or not?
>> 
>> I had second thoughts after my first response. A wrapped C++ method will
>> check to see if the C++ instance has been destroyed anyway, so there is
>> no
>> need to check it here as well.
>> 
>> However, a wrapped C++ method still won't work in exactly the same way
as
>> a
>> C++ application because the "C++ instance has been destroyed" flag has
to
>> be set before the destroyed() signal has been emitted. But it's ok for
>> pure
>> Python methods.
> 
> Hi Phil, it seems that the commit originated from this discussion (grep 
> "2008/07/26" in the sip changelog, just before 4.7.7 was released) 
> introduced a bug in how PyQt manages autodisconnection when delete is 
> called on a connected C++ object.  Please consider the following snippet:
> 
> ***********************************************************
> import sip
> from PyQt4.Qt import *
> 
> class Receiver(QObject):
>      def myslot(self):
>          print self.objectName()
> 
> emitter = QObject(None)
> receiver = Receiver(None)
> 
> QObject.connect(emitter, SIGNAL("TEST()"), receiver.myslot)
> emitter.emit(SIGNAL("TEST()")) # myslot gets called
> sip.delete(receiver)           # autodisconnection expected
> 
> # PyQt 4.4.2, SIP 4.7.6 - Disconnected as expected
> # PyQt 4.4.4, SIP 4.7.8/4.7.9 - Still connected
> 
> emitter.emit(SIGNAL("TEST()"))
> ***********************************************************
> 
> One would expect the runtime to break the connection after the 
> sip.delete() invocation, instead the connection is still in place with 
> recent versions of sip, so when the "TEST()" signal is emitted for the 
> second time, execution aborts with the feared "RuntimeError: underlying 
> C/C++ object has been deleted".
> 
> 
> If you test the same behaviour with a wrapped C++ slot, you'll see that 
> sip breaks the connection as expected.
> ***********************************************************
> import sip
> from PyQt4.Qt import *
> 
> app = QApplication([])
> emitter = QObject(None)
> receiver = QWidget(None)
> QObject.connect(emitter, SIGNAL("TEST()"), receiver, SLOT("show()"))
> emitter.emit(SIGNAL("TEST()"))
> sip.delete(receiver)
> emitter.emit(SIGNAL("TEST()"))
> ***********************************************************

I think the current behaviour is correct and consistent with how PyQt
handles disappearing C++ objects.

In your first case you are connecting to a Python callable which is still a
valid reference even though the underlying QObject has gone. PyQt will only
complain about that if you try to use it, as you are doing by calling
objectName().

If, instead, you had "del receiver" rather than "sip.delete(receiver)" then
the autodisconnect would happen.

If, as you show in the second example, you had connected to a Qt slot (eg.
by decorating myslot() with pyqtSignature) then you would also get the
autodisconnect.

Phil


More information about the PyQt mailing list