Passing extra arguments to PyQt slots

April 25th, 2011 at 1:38 pm

A frequent question coming up when programming with PyQt is how to pass extra arguments to slots. After all, the signal-slot connection mechanism only specifies how to connect a signal to a slot – the signal’s arguments are passed to the slot, but no additional (user-defined) arguments may be directly passed.

But passing extra arguments can be quite useful. You can have a single slot handling signals from multiple widgets, and sometimes you need to pass extra information.

One way to do this is using lambda. Here’s a complete code sample:

from PyQt4.QtCore import *
from PyQt4.QtGui import *

class MyForm(QMainWindow):
    def __init__(self, parent=None):
        super(MyForm, self).__init__(parent)
        button1 = QPushButton('Button 1')
        button2 = QPushButton('Button 1')
        button1.clicked.connect(lambda: self.on_button(1))
        button2.clicked.connect(lambda: self.on_button(2))

        layout = QHBoxLayout()
        layout.addWidget(button1)
        layout.addWidget(button2)

        main_frame = QWidget()
        main_frame.setLayout(layout)

        self.setCentralWidget(main_frame)

    def on_button(self, n):
        print('Button {0} clicked'.format(n))

if __name__ == "__main__":
    import sys
    app = QApplication(sys.argv)
    form = MyForm()
    form.show()
    app.exec_()

Note how the on_button slot is used to handle signals from both buttons. Here we use lambda to pass the button number to the slot, but anything can be passed – even the button widget itself (suppose the slot wants to deactivate the button that sent the signal).

There’s an alternative to lambda – using functools.partial. We can replace the connection lines with:

button1.clicked.connect(partial(self.on_button, 1))
button2.clicked.connect(partial(self.on_button, 2))

Which method is better? It’s really a matter of style. Personally, I prefer the lambda because it’s more explicit and flexible.

Related posts:

  1. Passing extra arguments to Qt slots
  2. New-style signal-slot connection mechanism in PyQt
  3. Creating splash screens in PyQt
  4. Problem passing arguments to Python scripts on Windows
  5. Code sample – socket client based on Twisted with PyQt

19 Responses to “Passing extra arguments to PyQt slots”

  1. Lauro MouraNo Gravatar Says:

    A potential problem for beginners with lambda is that it will not evaluate/look up the argument values before it is effectively called, breaking situations like using a loop variable inside it.

    AFAIR in PySide (an LGPL PyQt alternative) we had an user with exactly this problem, connecting a handful of buttons using lambdas in a for loop and clicking any button triggered only the last registered action.

  2. elibenNo Gravatar Says:

    Lauro,

    Interesting – thanks!

  3. anonymousNo Gravatar Says:

    But, does this work for SIGNALs that actually pass a value to the slot ???

  4. elibenNo Gravatar Says:

    anonymous,

    Sure – for those, the lambda has to take parameters (the amount the signal passes), and pass them to the slot along with the user’s extra parameters.

  5. Christopher ReillyNo Gravatar Says:

    Correct me if I’m wrong but I have noticed that lambdas are not disconnected from signals even when qt objects contained in the lambda are deleted, which could give a lot of errors.

  6. elibenNo Gravatar Says:

    Christopher,

    Can you find a concrete example that demonstrates this problem?

  7. F2KNo Gravatar Says:

    This is a very well written blog, keep up the good work!

    I’m working on an crossplatform embedded controller, communication through PySerial and a PyQt GUI. You just saved me a whole day of work :-) .

  8. JohannaNo Gravatar Says:

    Hi,
    Thanks for this useful tip. But I am having the exact situation Lauro described. I have several images in a scroll area generated in a loop and I want to connect each image to a function with a parameter depending on the loop. How can I do that ?

  9. elibenNo Gravatar Says:

    Johanna,

    What you can do is have another function generate the lambda, i.e. something like:

    def make_callback(param):
      return lambda: self.on_button(param)

    And in the connection, call make_callback(i). Then a different lambda is created for each iteration.

  10. JohannaNo Gravatar Says:

    Thanks for your response, but I didn’t get it to work yet.
    What I did is :
    in my loop thatcreates the images I do :

    image_label = ExtendedQLabel.ExtendedQLabel()
       vignette_image = QtGui.QImage(vignette_path)
       image_label.setPixmap(QtGui.QPixmap.fromImage(vignette_image))
       self.layout_scroll.addWidget(image_label)
       self.connect(image_label, QtCore.SIGNAL("clicked()"), lambda: self.make_callback(vignette))

    Then I define

    def make_callback(self, param):
            return lambda: self.imageClicked(param)`
    And      
    
    `def imageClicked(self, nom_plan):
            msgError = QtGui.QMessageBox()
            msgError.setText(nom_plan)
            msgError.exec_()

    What am I doing wrong ?

  11. elibenNo Gravatar Says:

    Johanna,

    self.connect(image_label, QtCore.SIGNAL("clicked()"), lambda: self.make_callback(vignette))

    I don’t think you need the lambda: here. connect expects a function as its third argument, and self.make_callback already returns a function.

  12. JohannaNo Gravatar Says:

    Great! Thank you very much for your help, you saved a lot of hair pulling over here :)

  13. pavanNo Gravatar Says:

    Hi , Thank you for this helpful blog
    I struggled how to pass multiple arguments in Qobject.connect , but finally
    it’s very helpful
    cheers

  14. GermánNo Gravatar Says:

    Hi, thanks for the post.

    I experienced some problems when I wanted to use the lambda solution for appending a value to a signal which is sending its own values. So, at the end I had to use the partial function.

    Do you know the way of doing so with lambda?

    Regards,

    Germán

  15. elibenNo Gravatar Says:

    Germán,

    Python lambda forms are syntactically restricted to a single expression. Anything that doesn’t count as a single expression can’t go into a lambda.

  16. AlexeyNo Gravatar Says:

    maybe wrong way, but that work for me and has ability to pass any amount of args

    def myb(txt, func, *args):
    	a = QPushButton(txt)
    	a.args = args
    	a.clicked.connect(func)
    	return a
    
    class main_W:
        def __init__:
              layout.addWidget(myb("button", self.func, 1,2,3,4,5,6))
    
       def func(self)
            args = self.sender().args
  17. pyros2097No Gravatar Says:

    Thanks Saved a lot of time .. . .

  18. Troy UnrauNo Gravatar Says:

    Thanks a lot for this post. I was fighting with the problem of passing extra arguments for a while until I found it. Many many thanks! :D

  19. TimNo Gravatar Says:

    Unfortunately, neither of these solutions seem to work when the destination object is in a different QThread. The object ends up executing in the main QThread when its given a lambda function, whereas it correctly executes in the worker thread when its given no arguments.

Leave a Reply

To post code with preserved formatting, enclose it in `backticks` (even multiple lines)