#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 ссылаются на отдельные переменные!).
Есть два популярных способа избежать этого:
- Использование параметра по умолчанию:
lambda i=i: self._number(i)
. Это работает, потому что параметры по умолчанию привязывают значение во время определения функции. - Определение вспомогательной функции
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 😉