¿Por qué la salida de las siguientes dos comprensiones de lista es diferente, aunque f y la función lambda son las mismas?

f = lambda x: x*x
[f(x) for x in range(10)]

Y

[lambda x: x*x for x in range(10)]

Eso sí, tanto type(f) como type(lambda x: x*x) devuelven el mismo tipo.

160
user763191 20 may. 2011 a las 22:39

6 respuestas

La mejor respuesta

El primero crea una sola función lambda y la llama diez veces.

El segundo no llama a la función. Crea 10 funciones lambda diferentes. Pone a todos esos en una lista. Para que sea equivalente al primero que necesita:

[(lambda x: x*x)(x) for x in range(10)]

O mejor aún:

[x*x for x in range(10)]
239
Winston Ewert 20 may. 2011 a las 18:41

La gran diferencia es que el primer ejemplo en realidad invoca la lambda f(x), mientras que el segundo ejemplo no.

Su primer ejemplo es equivalente a [(lambda x: x*x)(x) for x in range(10)] mientras que su segundo ejemplo es equivalente a [f for x in range(10)].

18
Gabe 20 may. 2011 a las 18:41

Esta pregunta toca una parte muy apestosa de la sintaxis de Python "famosa" y "obvia": lo que tiene prioridad, la lambda o la comprensión de la lista.

No creo que el objetivo del OP sea generar una lista de cuadrados de 0 a 9. Si ese fuera el caso, podríamos dar aún más soluciones:

squares = []
for x in range(10): squares.append(x*x)
  • Esta es la buena forma de sintaxis imperativa.

Pero no es el punto. El punto es W (hy) TF ¿es esta expresión ambigua tan contraintuitiva? Y tengo un caso idiota para ti al final, así que no descartes mi respuesta demasiado pronto (la tuve en una entrevista de trabajo).

Entonces, la comprensión del OP devolvió una lista de lambdas:

[(lambda x: x*x) for x in range(10)]

Por supuesto, esto es solo 10 copias diferentes de la función de cuadratura, consulte:

>>> [lambda x: x*x for _ in range(3)]
[<function <lambda> at 0x00000000023AD438>, <function <lambda> at 0x00000000023AD4A8>, <function <lambda> at 0x00000000023AD3C8>]

Tenga en cuenta las direcciones de memoria de las lambdas: ¡todas son diferentes!

Por supuesto, podría tener una versión más "óptima" (jaja) de esta expresión:

>>> [lambda x: x*x] * 3
[<function <lambda> at 0x00000000023AD2E8>, <function <lambda> at 0x00000000023AD2E8>, <function <lambda> at 0x00000000023AD2E8>]

¿Ver? 3 veces lo mismo lambda.

Tenga en cuenta que usé _ como la variable for. No tiene nada que ver con x en lambda (¡está eclipsado léxicamente!). ¿Consíguelo?

Estoy dejando de lado la discusión, por qué la precedencia de sintaxis no es así, que todo significaba:

[lambda x: (x*x for x in range(10))]

Que podría ser: [[0, 1, 4, ..., 81]], o [(0, 1, 4, ..., 81)], o que me parece más lógico , este sería un list de 1 elemento: un generator devolviendo los valores. Simplemente no es el caso, el lenguaje no funciona de esta manera.

PERO Qué, si ...

¿Qué pasa si NO eclipsas la variable for Y la usas en tus lambda s ???

Bueno, entonces pasa una mierda. Mira esto:

[lambda x: x * i for i in range(4)]

Esto significa, por supuesto:

[(lambda x: x * i) for i in range(4)]

PERO NO SIGNIFICA:

[(lambda x: x * 0), (lambda x: x * 1), ... (lambda x: x * 3)]

¡Esto es una locura!

Las lambdas en la lista de comprensión son un cierre sobre el alcance de esta comprensión. Un cierre léxico , por lo que se refieren a i por referencia, ¡y no a su valor cuando se evaluaron!

Entonces, esta expresión:

[(lambda x: x * i) for i in range(4)]

ES aproximadamente EQUIVALENTE a:

[(lambda x: x * 3), (lambda x: x * 3), ... (lambda x: x * 3)]

Estoy seguro de que podríamos ver más aquí usando un descompilador de Python (con lo que quiero decir, por ejemplo, el módulo dis), pero para la discusión independiente de Python-VM esto es suficiente. Esto en cuanto a la pregunta de la entrevista de trabajo.

Ahora, ¿cómo hacer un list de lambdas multiplicadores, que realmente se multiplican por enteros consecutivos? Bueno, de manera similar a la respuesta aceptada, necesitamos romper el vínculo directo con i envolviéndolo en otro lambda, que se llama dentro la expresión de comprensión de la lista:

Antes:

>>> a = [(lambda x: x * i) for i in (1, 2)]
>>> a[1](1)
2
>>> a[0](1)
2

Después:

>>> a = [(lambda y: (lambda x: y * x))(i) for i in (1, 2)]
>>> a[1](1)
2
>>> a[0](1)
1

(Tenía la variable lambda externa también = i, pero decidí que esta es la solución más clara: introduje y para que todos podamos ver qué bruja es cuál).

Editar 2019-08-30:

Siguiendo una sugerencia de @josoler, que también está presente en una respuesta de @sheridp: el valor de la "variable de bucle" de comprensión de la lista se puede "incrustar" dentro de un objeto; la clave es que se acceda en el momento adecuado. La sección "Después" anterior lo hace envolviéndolo en otro lambda y llamándolo inmediatamente con el valor actual de i. Otra forma (un poco más fácil de leer, no produce ningún efecto 'WAT') es almacenar el valor de i dentro de un objeto partial, y tener el "interior" (original) {{X4 }} tómalo como un argumento (aprobado suministrado por el objeto partial en el momento de la llamada), es decir:

Después de 2:

>>> from functools import partial
>>> a = [partial(lambda y, x: y * x, i) for i in (1, 2)]
>>> a[0](2), a[1](2)
(2, 4)

Genial, ¡pero todavía hay un pequeño giro para ti! Digamos que no queremos facilitar el lector de código y pasar el factor por nombre (como argumento de palabra clave a partial). Hagamos un cambio de nombre:

Después de 2.5:

>>> a = [partial(lambda coef, x: coef * x, coef=i) for i in (1, 2)]
>>> a[0](1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: <lambda>() got multiple values for argument 'coef'

WAT?

>>> a[0]()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: <lambda>() missing 1 required positional argument: 'x'

Espera ... ¿Estamos cambiando el número de argumentos por 1 y pasando de "demasiados" a "muy pocos"?

Bueno, no es un WAT real, cuando pasamos coef a partial de esta manera, se convierte en un argumento de palabra clave, por lo que debe venir después del argumento posicional x, así:

Después de 3:

>>> a = [partial(lambda x, coef: coef * x, coef=i) for i in (1, 2)]
>>> a[0](2), a[1](2)
(2, 4)

Preferiría la última versión sobre la lambda anidada, pero a cada una su propia ...

91
Tomasz Gandor 30 ago. 2019 a las 10:09

Las otras respuestas son correctas, pero si está intentando hacer una lista de funciones, cada una con un parámetro diferente, que se puede ejecutar más adelante , el siguiente código lo hará:

import functools
a = [functools.partial(lambda x: x*x, x) for x in range(10)]

b = []
for i in a:
    b.append(i())

In [26]: b
Out[26]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Si bien el ejemplo está ideado, lo encontré útil cuando quería una lista de funciones que cada una imprima algo diferente, es decir,

import functools
a = [functools.partial(lambda x: print(x), x) for x in range(10)]

for i in a:
    i()
4
sheridp 24 ene. 2017 a las 14:52

La gente dio buenas respuestas pero olvidó mencionar la parte más importante en mi opinión: En el segundo ejemplo, el X de la comprensión de la lista NO es el mismo que el X de la función lambda, no tienen ninguna relación. Entonces, el segundo ejemplo es en realidad el mismo que:

[Lambda X: X*X for I in range(10)]

Las iteraciones internas en range(10) solo son responsables de crear 10 funciones lambda similares en una lista (10 funciones separadas pero totalmente similares, devolviendo la potencia 2 de cada entrada).

Por otro lado, el primer ejemplo funciona totalmente diferente, porque la X de las iteraciones SI interactúa con los resultados, para cada iteración el valor es X*X por lo que el resultado sería [0,1,4,9,16,25, 36, 49, 64 ,81]

7
W.R. 25 sep. 2016 a las 20:01

El primero

f = lambda x: x*x
[f(x) for x in range(10)]

Ejecuta f() para cada valor en el rango, por lo que hace f(x) para cada valor

El segundo

[lambda x: x*x for x in range(10)]

Ejecuta el lambda para cada valor de la lista, por lo que genera todas esas funciones.

9
zellio 20 may. 2011 a las 18:42