Lambdas dentro de la lista de comprensiones.

Quería tener una lista de lambdas que actúan como una especie de caché para algunos cálculos pesados ​​y noté esto:

>>> [j() for j in [lambda:i for i in range(10)]] [9, 9, 9, 9, 9, 9, 9, 9, 9, 9] 

A pesar de que

 >>> list([lambda:i for i in range(10)]) [<function  at 0xb6f9d1ec>, <function  at 0xb6f9d22c>, <function  at 0xb6f9d26c>, <function  at 0xb6f9d2ac>, <function  at 0xb6f9d2ec>, <function  at 0xb6f9d32c>, <function  at 0xb6f9d36c>, <function  at 0xb6f9d3ac>, <function  at 0xb6f9d3ec>, <function  at 0xb6f9d42c>] 

Lo que significa que las lambdas son funciones únicas pero de alguna manera todas comparten el mismo valor de índice.

¿Es este un error o una característica? ¿Cómo evito este problema? No se limita a listas de comprensión …

 >>> funcs = [] ... for i in range(10): ... funcs.append(lambda:i) ... [j() for j in funcs] [9, 9, 9, 9, 9, 9, 9, 9, 9, 9] 

La lambda devuelve el valor de i en el momento en que lo llama. Ya que llama a la lambda después de que el bucle haya terminado de ejecutarse, el valor de i siempre será 9.

Puede crear una variable i local en la lambda para mantener el valor en el momento en que se definió la lambda :

 >>> [j() for j in [lambda i=i:i for i in range(10)]] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 

Otra solución es crear una función que devuelva la lambda :

 def create_lambda(i): return lambda:i >>> [j() for j in [create_lambda(i) for i in range(10)]] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 

Esto funciona porque hay un cierre diferente (que contiene un valor diferente de i ) creado para cada invocación de create_lambda .

Lo que estás viendo aquí es el efecto de los cierres . La lambda está capturando el estado del progtwig para ser utilizado más adelante. Entonces, mientras que cada lambda es un objeto único, el estado no es necesariamente único.

El ‘gotchya’ real aquí, es que la variable i se captura, no el valor que i representa en ese momento. Podemos ilustrar esto con un ejemplo mucho más fácil:

 >>> y = 3 >>> f = lambda: y >>> f() 3 >>> y = 4 >>> f() 4 

La lambda conserva la referencia a la variable y la evalúa cuando ejecuta lambda.

Para solucionar esto, puede asignar una variable local dentro de la lambda:

 >>> f = lambda y=y:y >>> f() 4 >>> y = 6 >>> f() 4 

Finalmente, en el caso de un bucle, la variable de bucle solo se ‘declara’ una vez. Por lo tanto, cualquier referencia a la variable de bucle dentro del bucle persistirá más allá de la siguiente iteración. Esto incluye la variable en listas de comprensión.

El problema es que no está capturando el valor de i en cada iteración de la comprensión de la lista, sino que está capturando la variable cada vez.

El problema es que un cierre captura variables por referencia. En este caso, está capturando una variable cuyo valor cambia con el tiempo (como con todas las variables de bucle), por lo que tiene un valor diferente cuando lo ejecuta que cuando lo creó.

No estoy seguro de si es un error o una característica, pero lo que ocurre es que lambda:i no evalúo i antes de configurar la función lambda. Así que, literalmente, es solo una función que evalúa el valor actual de i. Aquí hay otro ejemplo de cómo sucede esto.

 >>> i=5 >>> x=lambda:i >>> x() 5 >>> i=6 >>> x() 6 

Entonces, obviamente, lo que está sucediendo es lo mismo, excepto que i va a 9 en sus ejemplos, ya que se asigna a través del rango de 0 a 9 en ese orden.

No creo que realmente haya una buena manera de evitarlo. Las funciones Lambda en Python son bastante limitadas. No es realmente un lenguaje funcional en el corazón.

No estoy seguro de lo que intenta hacer con su función lambda. No hace falta una discusión …

Creo que Python está generando un generador similar a (i for i in range(10)) y luego lo itera. Después de que cuente 10 veces, el valor final de i es 9, y eso es lo que devuelve la función lambda.

¿Quería hacer algún tipo de objeto que arrojara los números de [0,9]? Porque si quieres hacer eso, solo usa xrange(10) , que devuelve un iterador que produce los números [0,9].

 def f(x): return x lst = [f(x) for x in xrange(10)] print(lst == range(10)) # prints True 

Nota: Creo que es una muy mala idea tratar de nombrar una función j() . Python usa j para indicar la parte imaginaria de un número complejo, y creo que una función llamada j podría confundir el analizador. Recibí algunos mensajes de error extraños tratando de ejecutar su código.