How to pass arguments to callback functions in PyQt

I have around 10 QAction (this number will vary in runtime) in a toolbar, which all will do same thing, but using different parameters. I am thinking to add parameter as an attribute to QAction object, and then, QAction's triggered signal will also send object's itself to the callback function, so that I could get required parameters for the function. I have actually 2 questions about this:

  • Can it be done?
  • Is there a better way of doing this?

How to pass arguments to callback functions in PyQt

You can use functools.partial from standart Python library. Example with QAction:

some_action.triggered.connect(functools.partial(some_callback, param1, param2))

You could use a lambda function associated to the GUI control's slot to pass extra arguments to the method you want to execute.

# Create the build button with its caption
self.build_button = QPushButton('&Build Greeting', self)
# Connect the button's clicked signal to AddControl
self.build_button.clicked.connect(lambda: self.AddControl('fooData'))
def AddControl(self, name):
    print name

Source: snip2code - Using Lambda Function To Pass Extra Argument in PyQt4


The best way to pass the arguments is to not pass them at all. You can use the dynamic nature of python, and set whatever data you need as your own properties on the widgets themselves, get the target widget in the handler using self.sender(), and then get whatever properties you need directly from the widget.

In this example five buttons are created and the required state is set on the button widget as my_own_data property:

import sys
from PyQt4 import QtGui, QtCore

class Main(QtGui.QMainWindow):

    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.centralwidget = QtGui.QWidget()
        self.vboxlayout = QtGui.QVBoxLayout()

        for idx in range(5):
            button = QtGui.QPushButton('button ' + str(idx), None)
            button.my_own_data = str(idx)  # <<< set your own property
            button.clicked.connect(self.click_handler)  # <<< no args needed
            self.vboxlayout.addWidget(button)

        self.centralwidget.setLayout(self.vboxlayout)
        self.setCentralWidget(self.centralwidget)
        self.show()

    def click_handler(self, data=None):
        if not data:
            target = self.sender()  # <<< get the event target, i.e. the button widget
            data = target.my_own_data  # <<< get your own property
        QtGui.QMessageBox.information(self, 
                                      "you clicked me!", 
                                      "my index is {0}".format(data))

app = QtGui.QApplication(sys.argv)
main = Main()
main.show()

sys.exit(app.exec_())

You can send the action object itself using a signal mapper. However, it may be better to simply send an identifier and do all the work within the signal handler.

Here's a simple demo script:

from PyQt4 import QtGui, QtCore

class Window(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.mapper = QtCore.QSignalMapper(self)
        self.toolbar = self.addToolBar('Foo')
        self.toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
        for text in 'One Two Three'.split():
            action = QtGui.QAction(text, self)
            self.mapper.setMapping(action, text)
            action.triggered.connect(self.mapper.map)
            self.toolbar.addAction(action)
        self.mapper.mapped['QString'].connect(self.handleButton)
        self.edit = QtGui.QLineEdit(self)
        self.setCentralWidget(self.edit)

    def handleButton(self, identifier):
        if identifier == 'One':
            text = 'Do This'
        elif identifier == 'Two':
            text = 'Do That'
        elif identifier == 'Three':
            text = 'Do Other'
        self.edit.setText(text)

if __name__ == '__main__':

    import sys
    app = QtGui.QApplication(sys.argv)
    window = Window()
    window.resize(300, 60)
    window.show()
    sys.exit(app.exec_())