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

Resuelto lukad asked hace 13 años • 3 respuestas

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 intento 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 imprimen '9'. ¿Por qué es así y cómo puedo solucionarlo?

lukad avatar Jan 02 '11 21:01 lukad
Aceptado

Así es simplemente cómo se definen el alcance, la búsqueda de nombres y los cierres en Python.

Python solo introduce nuevos enlaces en el espacio de nombres mediante asignaciones y listas de parámetros de funciones. ipor lo tanto, en realidad no está definido en el espacio de nombres de lambda, sino en el espacio de nombres de __init__(). En consecuencia, la búsqueda de nombres ien lambda termina en el espacio de nombres de __init__(), donde ieventualmente se vincula a 9. Esto se llama "cierre".

Puede solucionar esta semántica que, ciertamente, no es muy intuitiva (pero está bien definida) pasándola icomo argumento de palabra clave con un valor predeterminado. Como se dijo, los nombres en las listas de parámetros introducen nuevos enlaces en el espacio de nombres local, por lo que iinside the lambdaluego se vuelve independiente de iin .__init__():

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

ACTUALIZACIÓN: clickedtiene un checkedargumento predeterminado que anularía el valor de i, por lo que debe agregarse a la lista de argumentos antes del valor de la palabra clave.

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

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

Estoy usando una sintaxis de señal y ranura de estilo nuevo aquí simplemente por conveniencia, la sintaxis de estilo antiguo funciona igual.

 avatar Jan 02 '2011 15:01

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

Hay dos formas populares de 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 momento de la definición de la función.
  2. Definición de una función auxiliar helper = lambda i: (lambda: self._number(i))y uso helper(i)en el bucle. Esto funciona porque el "externo" ise evalúa en el momento ien que se vincula y, como se mencionó anteriormente, el próximo cierre creado en la siguiente invocación de helperse referirá a una variable diferente.
 avatar Jan 02 '2011 15:01