2011-09-09 8 views
16

en Python 2.6:extraño comportamiento: Lambda dentro de lista por comprensión

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

resultados en:

[3, 3, 3] 

que sería de esperar que la salida sea [1, 2, 3]. Obtengo exactamente el mismo problema incluso con un enfoque de comprensión sin lista. E incluso después de copiar m en una variable diferente.

¿Qué me estoy perdiendo?

+0

... pero esto funciona con iteradores. >>> l = (lambda: m para m en [1,2,3 ]) >>> [x() para x en l] – GeneralBecos

+0

Esto se debe a que un generador no crea todos sus valores a la vez, los crea cuando se solicitan. Una lista de comprensión y una expresión de generador no son lo mismo, aunque a menudo se pueden usar indistintamente. Hay situaciones (como esta) donde el comportamiento es _significativamente_ diferente. –

+0

por qué 'x()' no solo 'x' ?? ¿¿como es diferente?? – amyassin

Respuesta

14

Para hacer las lambdas recuerdan el valor de m, se puede usar una discusión con un valor por defecto:

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

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

+0

¡Impresionante! No pensé en establecer un valor predeterminado para la lambda. Buena publicación. –

-1

Lo noté también. Concluí que los lambda son creados solo una 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 verifica el id() de los elementos.

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

+0

La lambda no se crea una vez. '[lambda: m para m en [1,2,3]]' producirá tres lambdas separadas. –

+0

Sí, noté eso. Lo siento :) El inicio y el final de la identificación parecían iguales para mí ... – Gerenuk

5

En resumen, no desea hacer esto. Más específicamente, lo que está encontrando es un problema de orden de operaciones. Está creando tres lambda separados que devuelven m, pero ninguno se llama inmediatamente. Luego, cuando llegue a la comprensión de la lista externa y se les llame 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)] 
[<function <lambda> at 0x021EA230>, <function <lambda> at 0x021EA1F0>, <function <lambda> at 0x021EA270>] 

Esos son tres lambdas independientes.

Y, como una prueba más:

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

Una vez más, tres identificadores separados.

+0

Compruebe el id() de la lambda. Son todos iguales. – Gerenuk

+0

Esto significa que la lambda contiene una referencia a m en lugar del valor de m. ¿Es posible hacer que la lambda mantenga el valor de una variable? ¡Gracias! – GeneralBecos

+0

@GeneralBecos - No es que yo sepa, no, aunque si alguien más sabe de alguna manera y podría agregarlo como un comentario adicional, me complacería tomar nota de ello. A menos que hiera su resultado final, su aproximación con un generador es razonable, y esto produciría la salida esperada: '[x() para x in (lambda: m para m en [1,2,3])]' –

6

El efecto que se encuentra se llama closures, 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é tu código a una versión equivalente sin comprensiones o lambdas.

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

Por lo tanto, en este punto, inner_list tiene tres funciones en el mismo, y cada función, cuando se le llama, devolverá el valor de m. Pero el punto más destacado es que todos ven el mismo m, aunque m está cambiando, nunca lo miran hasta que se lo llama mucho más tarde.

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

En particular, puesto que la lista interior está construido por completo antes de la lista externa comienza a ser construido, m ya ha llegado a su último valor de 3, y las tres funciones ver que mismo valor.

3

Mire el __closure__ de las funciones. Los 3 puntos con el mismo objeto de celda, que mantiene una referencia al m del ámbito exterior:

>>> print(*[x.__closure__[0] for x in [lambda: m for m in [1,2,3]]], sep='\n') 
<cell at 0x00D17610: int object at 0x1E2139A8> 
<cell at 0x00D17610: int object at 0x1E2139A8> 
<cell at 0x00D17610: int object at 0x1E2139A8> 

Si no desea que sus funciones a tomar m como un argumento de palabra clave, de acuerdo con la respuesta de unubtu, usted podría en su lugar, use una 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] 
0

Personalmente, considero que esta es una solución más elegante. Lambda devuelve una función, por lo que si queremos usar la función, entonces deberíamos usarla. Es confuso usar el mismo símbolo para la variable 'anónimo' en la lambda y para el generador, por lo que en mi ejemplo uso un símbolo diferente para hacerlo más claro.

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

es más rápido también.

>>> 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 mapa:

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

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