Conexión de slots y señales en PyQt4 en un bucle

Estoy intentando construir una calculadora con PyQt4 y conectar las señales de ‘clic ()’ de los botones no funciona como se esperaba. Estoy creando mis botones para los números dentro de un bucle for donde bash conectarlos después.

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) 

Cuando hago clic en los botones, todos ellos imprimen ‘9’. ¿Por qué es así y cómo puedo arreglar esto?

Esto es solo, cómo se define el scope, la búsqueda de nombres y los cierres en Python.

Python solo introduce nuevos enlaces en el espacio de nombres a través de la asignación y de las listas de parámetros de funciones. Por lo tanto, i no está realmente definido en el espacio de nombres de la lambda , sino en el espacio de nombres de __init__() . La búsqueda de nombre para i en la lambda, por lo tanto, termina en el espacio de nombres de __init__() , donde i finalmente se vincula a 9 . Esto se llama “cierre”.

Puede evitar estas semánticas realmente no intuitivas (pero bien definidas) al pasar i como un argumento de palabra clave con valor predeterminado. Como se dijo, los nombres en las listas de parámetros introducen nuevos enlaces en el espacio de nombres local, así que i dentro de la lambda se vuelve independiente de i en .__init__() :

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

Una alternativa más legible, menos mágica es functools.partial :

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

Estoy usando la syntax de señal y ranura de nuevo estilo aquí simplemente por conveniencia, la syntax de estilo antiguo funciona de la misma manera.

Estás creando cierres. Los cierres realmente capturan una variable, no el valor de una variable. Al final de __init__ , i es el último elemento de range(0, 10) , es decir, 9 . Todas las lambdas que creó en este ámbito se refieren a esta i y solo cuando se invocan, obtienen el valor de i en el momento en que se invocan (sin embargo, ¡las invocaciones separadas de __init__ crean lambdas con referencia a variables separadas!).

Hay dos formas populares para evitar esto:

  1. Usando un parámetro predeterminado: lambda i=i: self._number(i) . Esto funciona porque los parámetros predeterminados vinculan un valor en el tiempo de definición de la función.
  2. Definir una función helper = lambda i: (lambda: self._number(i)) y usar helper(i) en el bucle. Esto funciona porque el “exterior” i se evalúa en el momento en que estoy vinculado y, como se mencionó anteriormente, el siguiente cierre creado en la siguiente invocación del helper se referirá a una variable diferente.

Use la forma Qt , use QSignalMapper en QSignalMapper lugar.