Comportamiento extraño: Lambda dentro de la lista de comprensión.

En Python 2.6:

[x() for x in [lambda: m for m in [1,2,3]]] 

resultados en:

 [3, 3, 3] 

Espero que la salida sea [1, 2, 3]. Tengo exactamente el mismo problema incluso con un enfoque de no comprensión de listas. E incluso después de copiar m en una variable diferente.

¿Qué me estoy perdiendo?

Para hacer que las lambdas recuerden el valor de m , podría usar un argumento con un valor predeterminado:

 [x() for x in [lambda m=m: m for m in [1,2,3]]] # [1, 2, 3] 

Esto funciona porque los valores predeterminados se establecen una vez, en el momento de la definición. Cada lambda ahora usa su propio valor predeterminado de m lugar de buscar el valor de m en un ámbito externo en el tiempo de ejecución de lambda.

El efecto que está encontrando se llama cierres , cuando define una función que hace referencia a variables no locales, la función conserva una referencia a la variable, en lugar de obtener su propia copia. Para ilustrar, expandiré su código en una versión equivalente sin comprensiones o lambdas.

 inner_list = [] for m in [1, 2, 3]: def Lambda(): return m inner_list.append(Lambda) 

Entonces, en este punto, inner_list tiene tres funciones en ella, y cada función, cuando se llama, devolverá el valor de m . Pero el punto sobresaliente es que todos ven la misma m , aunque m está cambiando, nunca la miran hasta que se la llama mucho más tarde.

 outer_list = [] for x in inner_list: outer_list.append(x()) 

En particular, dado que la lista interna se construye completamente antes de que la lista externa comience a construirse, m ya ha alcanzado su último valor de 3, y las tres funciones ven ese mismo valor.

Larga historia corta, no quieres hacer esto. Más específicamente, lo que estás encontrando es un problema de orden de operaciones. Estás creando tres lambda separados que todos devuelven m , pero ninguno de ellos se llama de inmediato. Luego, cuando llegas a la comprensión de la lista externa y a todos se les llama el valor residual de m es 3, el último valor de la comprensión de la lista interna.

— Para comentarios —

 >>> [lambda: m for m in range(3)] [ at 0x021EA230>,  at 0x021EA1F0>,  at 0x021EA270>] 

Esas son tres lambdas separadas.

Y, como evidencia adicional:

 >>> [id(m) for m in [lambda: m for m in range(3)]] [35563248, 35563184, 35563312] 

De nuevo, tres identificaciones separadas.

Mira el __closure__ de las funciones. Los 3 puntos apuntan al mismo objeto de celda, que mantiene una referencia a m desde el ámbito externo:

 >>> print(*[x.__closure__[0] for x in [lambda: m for m in [1,2,3]]], sep='\n')    

Si no desea que sus funciones tomen m como un argumento de palabra clave, según la respuesta de unubtu, podría utilizar un lambda adicional para evaluar m en cada iteración:

 >>> [x() for x in [(lambda x: lambda: x)(m) for m in [1,2,3]]] [1, 2, 3] 

Personalmente, me parece una solución más elegante. Lambda devuelve una función, por lo que si queremos usarla, deberíamos usarla. Es confuso usar el mismo símbolo para la variable ‘anónima’ en la lambda y para el generador, por lo que en mi ejemplo, uso un símbolo diferente para que sea más claro.

 >>> [ (lambda a:a)(i) for i in range(3)] [0, 1, 2] >>> 

también es más rápido

 >>> timeit.timeit('[(lambda a:a)(i) for i in range(10000)]',number=10000) 9.231263160705566 >>> timeit.timeit('[lambda a=i:a for i in range(10000)]',number=10000) 11.117988109588623 >>> 

pero no tan rápido como el mapa:

 >>> timeit.timeit('map(lambda a:a, range(10000))',number=10000) 5.746963977813721 

(Realicé estas pruebas más de una vez, el resultado fue el mismo, esto se hizo en Python 2.7, los resultados son diferentes en Python 3: las dos listas de comprensión son mucho más cercanas en rendimiento y ambas mucho más lentas, el mapa sigue siendo mucho más rápido).

También me di cuenta de eso. Llegué a la conclusión de que los lambda se crean una sola vez. Entonces, de hecho, su comprensión de la lista interna dará 3 funciones idénticas, todas relacionadas con el último valor de m.

Pruébalo y comprueba el id () de los elementos.

[Nota: esta respuesta no es correcta; ver los comentarios]