Почему этот код переключает только синий? (Python, PyQt)

#python #qt #pyqt

#python #qt #pyqt

Вопрос:

По какой-то причине выполнение этого кода переключает только синий цвет, хотя предполагается, что он переключает каждый из цветов по отдельности.

Этот код является моей версией примера кода на http://eli.thegreenplace.net/2011/04/25/passing-extra-arguments-to-pyqt-slot , это учебное пособие по PyQt, которое я только начал изучать.

 import sys
from PyQt5.QtWidgets import (QWidget, QPushButton, 
    QFrame, QApplication)
from PyQt5.QtGui import QColor


class Example(QWidget):
    red = False
    blue = False
    green = False 
    buttons = []

    def __init__(self):
        super().__init__()

        self.init_UI()


    def init_UI(self):    
        self.col = QColor(0, 0, 0)
        for x in range(0, 3):
            self.buttons.append(QPushButton('Red' if x == 0 else ('Green' if x == 1 else 'Blue'), self))
            self.buttons[x].setCheckable(True)
            self.buttons[x].move(10, 10   50 * x)
            self.buttons[x].clicked[bool].connect(lambda: self.set_color(x))

        self.square = QFrame(self)
        self.square.setGeometry(150, 20, 100, 100)
        self.square.setStyleSheet("QWidget { background-color: %s }" %  
            self.col.name())

        self.setGeometry(300, 300, 280, 170)
        self.setWindowTitle('Toggle button')
        self.show()

    def set_color(self, button):
        if button == 0: 
            if self.red == False: 
                self.red = True
                self.col.setRed(255)
            else:
                self.red = False
                self.col.setRed(0)
        elif button == 1:
            if self.green == False: 
                self.green = True
                self.col.setGreen(255)
            else:
                self.green = False
                self.col.setGreen(0)
        else:
            if self.blue == False: 
                self.blue = True
                self.col.setBlue(255)
            else:
                self.blue = False
                self.col.setBlue(0)

        self.square.setStyleSheet("QFrame { background-color: %s }" %
            self.col.name())
        print(self.col.name())  

if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())
  

Ответ №1:

Причина, по которой connect(lambda: self.set_color(x)) это не работает, заключается в том, что x будет вычисляться только при вызове лямбда-выражения, то есть при отправке сигнала, что будет намного позже, после завершения цикла. Таким образом, set_color() будет получено значение x во время отправки сигнала. В вашем коде это будет последнее значение, которое x было в цикле, т.е. 2.

Хотя ответ @Hi верен, я нахожу решение Ачаяна (упомянутое в комментарии) более явным и работает просто отлично (вопреки некоторым комментариям — я проверил это с помощью кода):

 for x in range(0, 3):
    ...
    self.buttons[x].clicked[bool].connect(partial(self.set_color, x))
  

Причина, по которой это работает, заключается в том, что x является аргументом вызова функции (функция, являющаяся functools.partial ), поэтому x вычисляется немедленно. Функция, возвращаемая с помощью, partial(f, a) when f является функцией одного аргумента, является функцией, g() которая не принимает аргументов и будет вызывать f(a) .

Комментарии:

1. Просто для ясности: мои комментарии были не о partial , а о неправильном использовании lambda в ответе, который теперь удален. Я указал правильное использование, которое заключается в следующем: connect(lambda checked, x=x: self.set_color(x)) . На самом деле это более явно, чем partial , потому что оно включает checked аргумент для параметра, отправленного clicked сигналом. Но очевидно, что любое решение работает так же хорошо.