[PyQt] How do you explicitly connect parameterless signals?

Phil Thompson phil at riverbankcomputing.com
Sat May 24 10:38:43 BST 2014


On 24/05/2014 12:08 am, Russell Warren wrote:
> I have some cases where I want to decorate my slots, where the
> decorator has a typical (*args, **kwargs) catch-all signature.
>  Unfortunately, when doing this, I'm unable to connect the signals
> and slots properly to a parameter-free overload when one with
> parameters is available.
> 
> From the docs I thought it could be explicitly done at the connection
> side with the use of:
> 
>     button.clicked[].connect(foo)
> 
> or it could be explicit at the slot instead (?) that we want the
> parameter-free one via:
> 
>     @pyqtSlot()
>     def foo():
> 
> but I can't get either of these to work.  When my button slot has
> *args in the params, I always get the 'checked' boolean.
> 
> How do I force PyQt5 to *not* connect up the version with the (bool, )
> signature?
> 
> Full example code is below, with a stripped down example of the
> decorator issue:
> 
> import sys
> from PyQt5 import QtWidgets
> from PyQt5.QtCore import pyqtSlot
> 
> app = QtWidgets.QApplication(sys.argv)
> 
> w = QtWidgets.QWidget()
> w.setWindowTitle('Signal connection checking')
> 
> b1 = QtWidgets.QPushButton("Connection to ()")
> b2 = QtWidgets.QPushButton("Connection to (checked)")
> b3 = QtWidgets.QPushButton("Connection to (*args)")
> 
> layout = QtWidgets.QVBoxLayout(w)
> layout.addWidget(b1)
> layout.addWidget(b2)
> layout.addWidget(b3)
> 
> def bogus_decorator(f):
>     def wrapper(*args):
>         print "wrapper called with args = %r" % (args, )
>         return f() #<-- have to do this to make the code work
>         #should be the code below, but it fails since we call with
> 
>         #an unexpected arg and get a corresponding TypeError.
>         #return f(*args) #<--- will fails since it passes an
> unexpected arg
>     return wrapper
> 
> def onVoidClick():
>     print "normal (void) clicked!"
> 
> def onBoolClick(checked):
>     print "normal (bool) clicked!"
> 
> @pyqtSlot() #<-- shouldn't this force the parameter-free overload?
> @bogus_decorator
> #@pyqtSlot()  #<--- order does not matter
> def onWrappedStarClick():
>     #this always gets called with the (bool) overload
>     print "Actual handler fired!"
> 
> b1.clicked.connect(onVoidClick)
> b2.clicked.connect(onBoolClick)
> 
> #How to connect this next one so that it works like onVoidClick()?
> # - explicitly defining the slot with @pyqtSlot() does not work
> # - explicitly connecting an overload with clicked[()].connect doesn't
> work
> b3.clicked.connect(onWrappedStarClick)
> #b3.clicked[()].connect(onStarClick)
> 
> w.show()
> 
> sys.exit(app.exec_())

Some clarifications...

Qt5 handles signals with default arguments slightly differently to Qt4. 
Consequently PyQt5 behaviour is different to PyQt4 and (IMHO) easier to 
understand.

Don't think about there being two versions of the signal - there is only 
one and it has a full set of arguments. The fact that some arguments are 
optional only comes into play when you emit the signal - you can omit 
the optional arguments.

pyqtSlot() only defines the slot's signature in the QMetaObject. Qt will 
consider this when finding a slot that is compatible with the signal. A 
slot that has a signature that matches, or is a sub-set of, the signal 
signature is compatible.

...but the real problem in your example is that pyqtSlot() can only be 
applied to methods of QObject sub-classes.

Phil


More information about the PyQt mailing list