Using lambda expression to connect slots in pyqt

I am trying to connect slots with lambda functions, but it's not working the way I expect. In the code below, I succeed in connecting the first two buttons correctly. For the second two, which I connect in a loop, this goes wrong. Someone before me had the same question (Qt - Connect slot with argument using lambda), but this solution doesn't work for me. I've been staring at my screen for a half hour, but I can't figure out how my code is different.

class MainWindow(QtGui.QWidget):
    def __init__(self):
        super(QtGui.QWidget, self).__init__()

        main_layout = QtGui.QVBoxLayout(self)

        # Works:
        self.button_1 = QtGui.QPushButton('Button 1 manual', self)
        self.button_2 = QtGui.QPushButton('Button 2 manual', self)
        main_layout.addWidget(self.button_1)
        main_layout.addWidget(self.button_2)

        self.button_1.clicked.connect(lambda x:self.button_pushed(1))
        self.button_2.clicked.connect(lambda x:self.button_pushed(2))

        # Doesn't work:
        self.buttons = []
        for idx in [3, 4]:
            button = QtGui.QPushButton('Button {} auto'.format(idx), self)
            button.clicked.connect(lambda x=idx: self.button_pushed(x))
            self.buttons.append(button)
            main_layout.addWidget(button)


    def button_pushed(self, num):
        print 'Pushed button {}'.format(num)

Pressing the first two buttons yields 'Pushed button 1' and 'Pushed button 2', the other two both yield 'Pushed button False', although I expected 3 and 4.

I also haven't understood the lambda mechanism completely. What exactly gets connected? A pointer to a function that is generated by lambda (with the parameter substituted in) or is the lambda function evaluated whenever the signal fires?


Solution 1:

The QPushButton.clicked signal emits an argument that indicates the state of the button. When you connect to your lambda slot, the optional argument you assign idx to is being overwritten by the state of the button.

Instead, make your connection as

button.clicked.connect(lambda state, x=idx: self.button_pushed(x))

This way the button state is ignored and the correct value is passed to your method.

Solution 2:

Beware! As soon as you connect your signal to a lambda slot with a reference to self, your widget will not be garbage-collected! That's because lambda creates a closure with yet another uncollectable reference to the widget.

Thus, self.someUIwidget.someSignal.connect(lambda p: self.someMethod(p)) is very evil :)