Phil Thompson wrote:
On Thu, 22 Jan 2009 15:56:28 +0100, Lorenzo Mancini <[email protected]>
wrote:
Phil Thompson wrote:
On Sat, 26 Jul 2008 21:07:09 +0200, Giovanni Bajo <[email protected]>
wrote:
On Sat, 2008-07-26 at 17:10 +0100, Phil Thompson wrote:
On Wed, 23 Jul 2008 15:42:18 +0200, Giovanni Bajo <[email protected]>
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.

You are right, decorating with @pyqtSignature and modifying the signal and slot signatures accordingly, I get the desired behaviour. Just for reference:

***********************************************************
import sip
from PyQt4.Qt import *

class Receiver(QObject):
    @pyqtSignature("const QString&")
    def myslot(self, arg):
        print arg + self.objectName()

emitter = QObject(None)
receiver = Receiver(None)

QObject.connect(emitter, SIGNAL("TEST(const QString&)"), receiver, SLOT("myslot(const QString&)"))
emitter.emit(SIGNAL("TEST(const QString&)"), "1") # myslot gets called
sip.delete(receiver) # autodisconnection expected

emitter.emit(SIGNAL("TEST(const QString&)"), "2") # Disconnected
***********************************************************

Thanks!

--
Lorenzo Mancini
Develer S.r.l.
http://www.develer.com/
_______________________________________________
PyQt mailing list    [email protected]
http://www.riverbankcomputing.com/mailman/listinfo/pyqt

Reply via email to