[PyQt] Weird behaviour of destroyed() slots

Lorenzo Mancini lmancini at develer.com
Thu Jan 22 14:56:28 GMT 2009


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()"))
***********************************************************

-- 
Lorenzo Mancini
Develer S.r.l.
http://www.develer.com/


More information about the PyQt mailing list