#python #qt #pyqt #pyqt5
Вопрос:
Я пытаюсь создать калькулятор с PyQt4, и подключение сигналов ‘clicked ()’ от кнопок работает не так, как ожидалось. Я создаю свои кнопки для чисел внутри цикла 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
then становится независимым от 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. Спасибо. Я пойду с functools.частичное решение.
Ответ №2:
Вы создаете замыкания. Замыкания действительно фиксируют переменную, а не значение переменной. В конце __init__
, i
является последним элементом range(0, 10)
, т.е. 9
. Все лямбды, которые вы создали в этой области, ссылаются на this 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, в котором нет лямбд или частичных функций и где такой обходной путь является необходимым злом. Однако в PythonQSignalMapper
это просто лишнее и действительно раздутое по сравнению сfunctools.partial()
or lambda. Решения сpartial()
orlambda
короче и элегантнее, чемQSignalMapper
.2. Ну, что касается выбора, я бы выбрал
QSignalMapper
совместимость с Qt / C .3. Конечно, речь идет о выборе, но я просто не понимаю вашего выбора и не могу следовать ему. Я использую Python для приложений Qt именно потому, что это не C , и если бы я отказался от всех специальных функций Python для поддержания или достижения совместимости с C , я мог бы с таким же успехом использовать C 😉