Ustawianiem lambdy jako callbaca dla QtGui.QPushButton

0

Hej.

Chcę sobie napisać w pythonie aplikację, w której wyświetlam zdjęcia i po kliknięciu przycisku "coś z nimi robię". Wybrałem qt4 jako framework gui. Chcę do funkcji zwrotnej odpalanej po kliknięciu przycisku przekazać dodatkowy parametr, więc przekazuję lambdę. I tu właśnie problem. Może po prostu pokażę o co chodzi. Kod:

import os
import sys
from PyQt4 import QtGui, QtCore

class Example(QtGui.QWidget):
    
    def __init__(self, fnPrefix, image):
        super(Example, self).__init__()
        self.fnPrefix_ = fnPrefix
        self.image_ = image
        self.initUI()
        
        
    def initUI(self):
        vbox = QtGui.QVBoxLayout()
        vbox.addStretch(1)

        for fnTuple in self.image_:
            hbox = QtGui.QHBoxLayout()
            hbox.addStretch(1)

            imageName = fnTuple[0]
            button = QtGui.QPushButton("Choose '" + imageName + "'", self)
            button.clicked.connect(lambda: self.chooseImage(imageName))
            hbox.addWidget(button)
            vbox.addLayout(hbox)

        mygroupbox = QtGui.QGroupBox(self.fnPrefix_)
        mygroupbox.setLayout(vbox)
        scroll = QtGui.QScrollArea()
        scroll.setWidget(mygroupbox)
        scroll.setWidgetResizable(True)
        scroll.setFixedWidth(700)
        mainLayout = QtGui.QVBoxLayout()
        mainLayout.addWidget(scroll)
        self.setLayout(mainLayout) 

        self.setGeometry(200, 000, 800, 1000)
        self.setWindowTitle(self.fnPrefix_)    
        self.show()

    def chooseImage(self, name):
        print('Name of presed button: ' + name)

        #validation
        sender = self.sender()
        print('Button with text ' + sender.text() + ' was pressed')

def main():
    app = QtGui.QApplication(sys.argv)
    ex = Example(
        "image1",
        [
            ("_400x300", "~/full/path/image1_400x300"),
            ("_800x600", "~/full/path/image1_800x600"),
            ("_1024x768", "~/full/path/image1_1024x768"),
            ("_1280x1024", "~/full/path/image1_1280x1024")
        ])
    app.exec_()
    return 0


if __name__ == '__main__':
    sys.exit(main())

Wygląda na to, że zawsze zostaje przekazany ostatnio iterowany element. Odpalony skrypt wyświetla te 4 przyciski, ale po ich kolejnym wyklikaniu na konsolę zostaje wypisane:

rr@rr-debian:~/code/python$ python3 err.py 
Name of presed button: _1280x1024
Button with text Choose '_400x300' was pressed
Name of presed button: _1280x1024
Button with text Choose '_800x600' was pressed
Name of presed button: _1280x1024
Button with text Choose '_1024x768' was pressed
Name of presed button: _1280x1024
Button with text Choose '_1280x1024' was pressed

Nie korzystałem wcześniej z qt pod pythonem, a pythona znam tylko podstawowo, więc proszę o wyrozumiałość, jeśli to coś prostego;)

1

Wewnątrz lambdy jest umieszczana referencja do zmiennej zamiast jej zawartość.
Zawartość zostaje pobrana dopiero przy wywołaniu funkcji lambda.
Wywołanie funkcji lambda zdarza się po zakończeniu iteracji
gdy zmienna zawiera ostatnią wartość stąd taki dziwny efekt.

Gdybyś po iteracji jeszcze gdzieś zmienił zawartość zmiennej użytej w iteracji
to zmieniło by się też działanie funkcji lambda.

Zamiast

lambda:funkcja(i)

trzeba zrobić

lambda x=i:funkcja(x)

bo to powoduje natychmiastowe pobranie zawartości zmiennej 'i'
i przypisanie do zmiennej x


Można też zapisać w trochę mniej czytelny sposób

lambda i=i:funkcja(i)
0
furas napisał(a):

Zamiast

lambda:funkcja(i)

trzeba zrobić

lambda x=i:funkcja(x)

bo to powoduje natychmiastowe pobranie zawartości zmiennej 'i'
i przypisanie do zmiennej x


Można też zapisać w trochę mniej czytelny sposób

lambda i=i:funkcja(i)

Tak jak myślałem, braki z ogólnej wiedzy o pythonie, czas w końcu jakąś książkę przeczytać. Zrobiłem jak pisałeś, miałem jeszcze jeden problem z tym, że parametr name konwertował mi się do boola - ale to wynika z tego, że callback w api qt ma domyślny parametr boolowy. Ostatecznie taka postać działa:

button.clicked.connect(lambda b, imgN=imageName: self.chooseImage(b, imgN))
# (...)
def chooseImage(self, unknownBool, name):

Dzięki

1 użytkowników online, w tym zalogowanych: 0, gości: 1