Pregunta Python: función Lambda en Comprensión de listas


¿Por qué el resultado 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)]

Tenga en cuenta que tanto el tipo (f) como el tipo (lambda x: x * x) devuelven el mismo tipo.


75
2018-05-20 18:39


origen


Respuestas:


El primero crea una única función lambda y lo llama diez veces.

El segundo no llama a la función. Crea 10 funciones lambda diferentes. Pone 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)]

153
2018-05-20 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 lista de comprensión.

No creo que el propósito del OP fuera generar una lista de cuadrados del 0 al 9. Si ese fuera el caso, podríamos ofrecer 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 estúpido para usted al final, así que no descarte mi respuesta demasiado pronto (la tuve en una entrevista de trabajo).

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

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

Esto es por supuesto solo 10 diferente copias de la función de cuadratura, ver:

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

Nota 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 Tiempo lo mismo lambda.

Tenga en cuenta que yo usé _ como el for variable. No tiene nada que ver con el x en el lambda (se eclipsó léxicamente!). ¿Consíguelo?

Voy a dejar de lado la discusión, por qué la precedencia de la 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, esto sería un list de 1 elemento - a generator devolviendo los valores. Simplemente no es el caso, el lenguaje no funciona de esta manera.

PERO Y si...

¿Qué pasa si NO eclipsa el for variable, Y úsala en tu lambdas ???

Bueno, entonces sucede una mierda. Mira este:

[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 léxico cierre, por lo que se refieren a la i a través de referencia, ¡y no su valor cuando fueron evaluados!

Entonces, esta expresión:

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

ES MÁS 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 decompilador python (con lo cual me refiero, por ejemplo, a dis módulo), pero para la discusión de Python-VM-agnostic esto es suficiente. Demasiado para la pregunta de la entrevista de trabajo.

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

Antes de:

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

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


47
2017-12-01 13:14



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

Tu 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)].


17
2018-05-20 18:41



El primero

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

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

el segundo

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

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


7
2018-05-20 18:42



La gente dio buenas respuestas, pero se olvidó de mencionar la parte más importante en mi opinión: En el segundo ejemplo, X de la lista de comprensión NO es lo mismo que X del lambda función, no tienen ninguna relación. Entonces, el segundo ejemplo es realmente 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 DO 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]


2
2017-08-27 15:46



Las otras respuestas son correctas, pero si intenta hacer una lista de funciones, cada una con un parámetro diferente, que se puede ejecutar luego, el siguiente código hará eso:

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 es artificial, lo encontré útil cuando quería una lista de funciones que cada una imprime algo diferente, es decir,

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

for i in a:
    i()

2
2018-01-24 14:52