Подключение слотов и сигналов в PyQt4 в петле - PullRequest
9 голосов
/ 02 января 2011

Я пытаюсь построить калькулятор с 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». Почему это так и как я могу это исправить?

Ответы [ 3 ]

14 голосов
/ 02 января 2011

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

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

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

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

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

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

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

3 голосов
/ 02 января 2011

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

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

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

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

...