Generando funciones dentro del bucle con expresión lambda en python

Si hago dos listas de funciones:

def makeFun(i): return lambda: i a = [makeFun(i) for i in range(10)] b = [lambda: i for i in range(10)] 

¿Por qué las listas a y b no son iguales?

Por ejemplo:

 >>> a[2]() 2 >>> b[2]() 9 

Técnicamente, la expresión lambda se cierra sobre la i que es visible en el ámbito global, que se estableció por última vez en 9. Es la misma que se menciona en las 10 lambdas. Por ejemplo,

 i = 13 print b[3]() 

En la función makeFun , la lambda se cierra en la i que se define cuando se invoca la función. Esos son diez diferentes .

Como han dicho otros, el problema es el scope. Tenga en cuenta que puede resolver esto agregando un argumento adicional a la expresión lambda y asignándole un valor predeterminado:

 >> def makeFun(i): return lambda: i ... >>> a = [makeFun(i) for i in range(10)] >>> b = [lambda: i for i in range(10)] >>> c = [lambda i=i: i for i in range(10)] # <-- Observe the use of i=i >>> a[2](), b[2](), c[2]() (2, 9, 2) 

El resultado es que i ahora se coloca explícitamente en un ámbito limitado a la expresión lambda .

Un conjunto de funciones (a) opera en el argumento pasado y el otro (b) opera en una variable global que luego se establece en 9. Verifique el desassembly:

 >>> import dis >>> dis.dis(a[2]) 1 0 LOAD_DEREF 0 (i) 3 RETURN_VALUE >>> dis.dis(b[2]) 1 0 LOAD_GLOBAL 0 (i) 3 RETURN_VALUE >>> 

Para agregar algo de claridad (al menos en mi mente)

 def makeFun(i): return lambda: i a = [makeFun(i) for i in range(10)] b = [lambda: i for i in range(10)] 

a usa makeFun (i) que es una función con un argumento.

b usa lambda: i que es una función sin argumentos. La i que utiliza es muy diferente a la anterior.

Para hacer que a y b sean iguales podemos hacer que ambas funciones no usen argumentos:

 def makeFun(): return lambda: i a = [makeFun() for i in range(10)] b = [lambda: i for i in range(10)] 

Ahora ambas funciones usan el i global

 >>> a[2]() 9 >>> b[2]() 9 >>> i=13 >>> a[2]() 13 >>> b[2]() 13 

O (más útil) haga que ambos usen un argumento:

 def makeFun(x): return lambda: x a = [makeFun(i) for i in range(10)] b = [lambda x=i: x for i in range(10)] 

Cambié deliberadamente i con x donde la variable es local. Ahora:

 >>> a[2]() 2 >>> b[2]() 2 

El exito

Lambdas en python comparte el ámbito variable en el que se crearon. En su primer caso, el scope de la lambda es makeFun’s. En su segundo caso, es la i global, que es 9 porque es un remanente del bucle.

Eso es lo que entiendo de todos modos …

Buena atrapada. La lambda en la lista de comprensión está viendo la misma i local cada vez.

Puedes reescribirlo como:

 a = [] for i in range(10): a.append(makefun(i)) b = [] for i in range(10): b.append(lambda: i) 

Con el mismo resultado.