Using lambda expression to connect slots in pyqt

Viewed 35359

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?

4 Answers

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 :)

It's very simple. Check the working code and not working code. You have a syntax error.

Working code:

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

Doesn't work:

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

Fix:

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

for lambda you are defining an "x" function and explaining the function to the program as "self.button_pushed(idx)" in order to use arguments with your function in this case the argument is (idx). Just try and let me know if it works.

The issue he has is that he is trying to get different outputs from a for loop creation. Unfortunately, it assigns the last value to any variable named button, so it gives 4 as a result. First two are working because they are not created in a for loop but created individually.

And the names of the button variables are different in the working ones as button_1 and button_2. In the for loop all buttons created will have the name button which results in the same function.

The solution for what he is trying to do is below and it works like a charm.

from sys import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

buttons = []

def newWin():
    window = QWidget()
    window.setWindowTitle("Lambda Loop")
    window.setFixedWidth(1000)
    window.move(175, 10)
    window.setStyleSheet("background: #161219;")
    grid = QGridLayout()
    return window, grid

def newButton(text :str, margin_left, margin_right, x):
    button = QPushButton(text)
    button.setCursor(QCursor(Qt.PointingHandCursor))
    button.setFixedWidth(485)
    button.setStyleSheet(
        "*{border: 4px solid '#BC006C';" +
        "margin-left: " + str(margin_left) + "px;" +
        "margin-right: " + str(margin_right) + "px;" +
        "color: 'white';" +
        "font-family: 'Comic Sans MS';" +
        "font-size: 16px;" +
        "border-radius: 25px;" +
        "padding: 15px 0px;" +
        "margin-top: 20px;}" +
        "*:hover {background: '#BC006C'}"
    )

    def pushed():
        val = x
        text = QLabel(str(val))
        text.setAlignment(Qt.AlignRight)
        text.setStyleSheet(
            "font-size: 35px;" +
            "color: 'white';" +
            "padding: 15px 15px 15px 25px;" +
            "margin: 50px;" +
            "background: '#64A314';" +
            "border: 1px solid '#64A314';" +
            "border-radius: 0px;"
        )
        grid.addWidget(text, 1, 0)
    button.clicked.connect(pushed)
    return button

app = QApplication(argv)
window, grid = newWin()

def frame1(grid):
    for each in [3, 4]:
        button = newButton('Button {}'.format(each), 150, 150, each)
        buttons.append(button)
        pass
    b_idx = 0
    for each in buttons:
        grid.addWidget(each, 0, b_idx, 1, 2)
        b_idx += 1

frame1(grid)

window.setLayout(grid)

window.show()

exit(app.exec())

I put all in one place so that all can see. Tell what you want to do is easier than speculations. (You can also add new variables in the list in Frame function and it will create more buttons for you with different values and functions.)

Related