Найти - Пользователи
Полная версия: PyQt4. Динамическое создание кнопок. Проблема с передачей атрибута через сигнал.
Начало » GUI » PyQt4. Динамическое создание кнопок. Проблема с передачей атрибута через сигнал.
1
Pluto
from PyQt4 import QtGui
class win(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)
        
        self.lay = QtGui.QVBoxLayout()
        self.setLayout(self.lay)
        
        self.mass = [{"obj":None, "name":"one", "text":"Первая кнопка", "val":1},
                   {"obj":None, "name":"two", "text":"Вторая кнопка", "val":2}]
        for i in self.mass:
            i["obj"] = QtGui.QPushButton(i["text"])
            i["obj"].setObjectName(i["name"])
            self.lay.addWidget(i["obj"])
            i["obj"].clicked.connect(lambda: self.myfunc(i["val"])) # как тут сделать-то?
    def myfunc(self, val):
        print (str(val))
if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    window = win()
    window.show()
    sys.exit(app.exec_())

Кнопки прекрасно создаются, каждая как отдельный объект. А вот связать каждую кнопку с методом не выходит. Лямбда-блямбда, в итоге, для всех кнопок передаёт последнее значение из списка. Т.е. при нажатии на первую кнопку на экране должна бы появляться единичка, но вторую - двойка; а выходит так, что куда не тычь - получишь двойку.
Как победить?
vrabey
в цикле значение i на каждой итерации перезаписывается
for i in range(3):
    pass
print i

нужно как нибудь хранить промежуточное состояние
lst = []
for i in range(3):
    lst.append(i)
print lst[0]
Pluto
Что-то не пойму.
Как это применимо к моей программе?
i на каждой итерации содержит ссылку на словарь из списка. Я использую значения именно из словаря.
Pluto
или мне надо в каждый словарь ещё и лямбду как отдельный элемент засунуть и к нему сигналы кнопок коннектить?
Pluto
from PyQt4 import QtGui
class win(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)
        
        self.lay = QtGui.QVBoxLayout()
        self.setLayout(self.lay)
        
        self.mass = [{"obj":None, "name":"one", "text":"Первая кнопка", "val":lambda:self.myfunc(1)}, # эвона как!
                   {"obj":None, "name":"two", "text":"Вторая кнопка", "val:"lambda:self.myfunc(2)}]
        for i in self.mass:
            i["obj"] = QtGui.QPushButton(i["text"])
            i["obj"].setObjectName(i["name"])
            self.lay.addWidget(i["obj"])
            i["obj"].clicked.connect(i["val"]) # вот так срабатывает верно
    def myfunc(self, val):
        print (str(val))
if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    window = win()
    window.show()
    sys.exit(app.exec_())

Вот так работает как надо, только выглядит это как-то…

Кто объяснит как оно так в питоне работает-то в моём первом варианте? Там что, получается, создавалась, а потом переопределялась одна и та же lambda-функция что ль? Которая, в последней своей модификации, оказалась привязана ко всем кнопкам. А почему одна и та же?

А как-то по-другому, более красиво сию ситуацию разрулить можно?
alex925
Ну первое, что приходит в голову это замыкания
import sys
from PyQt4 import QtGui
 
 
def wrapper(*args):
    def myfunc():
        print(*args)
    return myfunc
 
 
class Window(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)
        self.lay = QtGui.QVBoxLayout()
        self.setLayout(self.lay)
        self.mass = [{"obj": None, "name": "one", "text": "Первая кнопка", "val": 1},
                     {"obj": None, "name": "two", "text": "Вторая кнопка", "val": 2}]
        for i in self.mass:
            i["obj"] = QtGui.QPushButton(i["text"])
            i["obj"].setObjectName(i["name"])
            self.lay.addWidget(i["obj"])
            i["obj"].clicked.connect(wrapper(i["val"]))
 
 
if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())
alex925
Твой код с lambda не работает, потому что lambda реализует ленивые вычисления, то есть функция инициализируется в не в момент её создания, а момент вызова (и получается, что все созданные функции инициализируются с последним значением переменной цикла).
Именно по этому в цикле анонимные функции для откладывания вызова функции использовать не получится.
vrabey
так можно
from functools import partial # <<<
from PyQt4 import QtGui
class win(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)
        self.lay = QtGui.QVBoxLayout()
        self.setLayout(self.lay)
        self.mass = [{"obj":None, "name":"one", "text":u"Первая кнопка", "val":1},
                   {"obj":None, "name":"two", "text":u"Вторая кнопка", "val":2}]
        for i in self.mass:
            i["obj"] = QtGui.QPushButton(i["text"])
            i["obj"].setObjectName(i["name"])
            self.lay.addWidget(i["obj"])
            i["obj"].clicked.connect(partial (self.myfunc, i["val"])) # <<<
    def myfunc(self, val):
        print (str(val))
if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    window = win()
    window.show()
    sys.exit(app.exec_())
Pluto
Спасибо всем ответившим.
Vrabey отдельное БОЛЬШОЕ спасибо! Все проблемы с этим способом решились.
Приведённый тут мной пример был упрощен. В оригинальном коде мне понадобилось в качестве аргумента передать вообще ссылку на весь словарь, а не на отдельное значение его. И получилось без проблем!
partial - наше всё.
py.user.next
Pluto
i["obj"].clicked.connect(lambda: self.myfunc(i["val"])) # как тут сделать-то?

i["obj"].clicked.connect(lambda e, i=i: self.myfunc(i["val"]))

[guest@localhost py]$ ./t.py 
1
2
1
2
[guest@localhost py]$
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB