[PyQt] PyQt should not ignore functools.partial signature

Phil Thompson phil at riverbankcomputing.com
Fri Oct 14 10:21:34 BST 2011


On Fri, 14 Oct 2011 11:10:03 +0200, Luper Rouch <luper.rouch at gmail.com>
wrote:
> 2011/10/13 Phil Thompson <phil at riverbankcomputing.com>
> 
>> On Wed, 12 Oct 2011 15:57:17 +0200, Luper Rouch <luper.rouch at gmail.com>
>> wrote:
>> > PyQt seems to ignore the signature of functools.partial objects (the
>> 'args'
>> > and 'keywords' attributes [1]), when connecting a callable. Here is
an
>> > example demonstrating the problem :
>> >
>> > import functools
>> > from PyQt4.QtCore import QObject, pyqtSignal
>> >
>> > class Sender(QObject):
>> >
>> >     hello = pyqtSignal(bool)
>> >
>> > def receiver():
>> >     print "foo"
>> >
>> > def decorator(func):
>> >     @functools.wraps(func)
>> >     def wrapped(*args, **kwargs):
>> >         return func(*args, **kwargs)
>> >     return wrapped
>> >
>> > decorated_receiver = decorator(receiver)
>> >
>> > if __name__ == "__main__":
>> >     sender = Sender()
>> >     sender.hello.connect(receiver)
>> >     sender.hello.connect(decorated_receiver)
>> >     sender.hello.emit(True)
>> >
>> > When executed, the script gives the following error :
>> >
>> > $ python test_signature.py
>> > foo
>> > Traceback (most recent call last):
>> >   File "test_signature.py", line 17, in wrapped
>> >     return func(*args, **kwargs)
>> > TypeError: receiver() takes no arguments (1 given)
>> >
>> > Connecting to a lambda is not a good solution, because PyQt increases
>> its
>> > reference count to keep it alive ("However, if a slot is a lambda
>> function
>> > or a partial function then its reference count is automatically
>> incremented
>> > to prevent it from being immediately garbage collected", see [2]).
>> >
>> > If you do this in a widget that is later deleted, the lambda stays
>> alive,
>> > leading to complex bugs. The only solution is to connect to a normal
>> method
>> > calling the decorated method, which can quickly become cumbersome.
>> >
>> > It would be nice if PyQt did some additional checks when connecting
to
>> > a
>> > functools.partial object.
>>
>> Like what?
>>
>> The problem isn't when connecting but when emitting. PyQt emulates the
Qt
>> behaviour of allowing a slot to take fewer arguments than the signal is
>> providing. It does this by detecting a TypeError raised by the act of
>> calling the slot. In this case the exception is raised in the body of
the
>> slot and there is no way for PyQt to distinguish between that and any
>> other
>> TypeError raised while executing the slot.
>>
>>
> Maybe with a special path for partials and the likes:
> 
> if hasattr(slot, "args"):
>     # use slot.args to adapt slot call
> else:
>     # call the slot the normally
> 
> Could you please point me where the slot calling code lives so I can
have a
> better understanding of how things work ?

It's part of sip.  sip_api_invoke_slot() in qtlib.c

Phil


More information about the PyQt mailing list