Подключение слотов и сигналов в PyQt4 в цикле

#python #qt #for-loop #variables #pyqt5

Вопрос:

Я пытаюсь создать калькулятор с PyQt4, и подключение сигналов «нажал ()» с кнопок работает не так, как ожидалось. Я создаю свои кнопки для чисел внутри цикла for, где я пытаюсь соединить их впоследствии.

 def __init__(self):
    for i in range(0,10):
        self._numberButtons  = [QPushButton(str(i), self)]
        self.connect(self._numberButtons[i], SIGNAL('clicked()'), lambda : self._number(i))

def _number(self, x):
    print(x)
 

Когда я нажимаю на кнопки, все они выводят «9».
Почему это так и как я могу это исправить?

Ответ №1:

Это просто то, как область действия, поиск имен и замыкания определяются в Python.

Python вводит новые привязки в пространство имен только через назначение и списки параметров функций. i поэтому фактически определяется не в пространстве имен the lambda , а в пространстве имен of __init__() . Поиск имени i в лямбде , следовательно, заканчивается в пространстве __init__() имен, к которому i в конечном итоге привязывается 9 . Это называется «закрытием».

Вы можете обойти эту, по общему признанию, не совсем интуитивную (но четко определенную) семантику, передав i в качестве аргумента ключевое слово со значением по умолчанию. Как уже говорилось, имена в списках параметров вводят новые привязки в локальное пространство имен, поэтому i внутри lambda затем становится независимым от i in .__init__() :

 self._numberButtons[i].clicked.connect(lambda i=i: self._number(i))
 

Более читаемой, менее волшебной альтернативой является functools.partial :

 self._numberButtons[i].clicked.connect(partial(self._number, i))
 

Я использую здесь синтаксис сигналов и слотов нового стиля просто для удобства, синтаксис старого стиля работает точно так же.

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

1. Спасибо. Я пойду с функциями.частичное решение.

Ответ №2:

Вы создаете замыкания. Замыкания действительно фиксируют переменную, а не значение переменной. В конце __init__ , i находится последний элемент range(0, 10) , т. е. 9 . Все лямбды, созданные вами в этой области, ссылаются на это i , и только при их вызове они получают значение i в момент их вызова (однако отдельные вызовы __init__ create lambdas ссылаются на отдельные переменные!).

Есть два популярных способа избежать этого:

  1. Использование параметра по умолчанию: lambda i=i: self._number(i) . Это работает, потому что параметры по умолчанию привязывают значение во время определения функции.
  2. Определение вспомогательной функции helper = lambda i: (lambda: self._number(i)) и ее использование helper(i) в цикле. Это работает, потому что «внешний» i оценивается в момент i привязки, и, как упоминалось ранее, следующее закрытие, созданное при следующем helper вызове, будет ссылаться на другую переменную.

Ответ №3:

Используй Qt способ, используй QSignalMapper вместо этого.

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

1. Пожалуйста, не надо. QSignalMapper является остатком C 98, в котором нет лямбд или частичных функций и где такой обходной путь является необходимым злом. В Python, однако QSignalMapper , это просто излишне и действительно раздуто по сравнению с functools.partial() или лямбда. Решения с partial() или lambda короче и элегантнее, чем QSignalMapper .

2. О, ну, что касается выбора, я бы выбрал QSignalMapper совместимость с Qt/C .

3. Конечно, речь идет о выборе, но я просто не понимаю вашего выбора и не могу следовать ему. Я использую Python для приложений Qt именно потому, что это не C , и если бы я отказался от всех специальных функций Python для поддержания или достижения совместимости с C , я мог бы с таким же успехом использовать C 😉