Saltar al contenido

Función lambda en listas por comprensión

Solución:

El primero crea una única 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 necesitas:

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

O mejor aún:

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

Esta pregunta toca una parte muy apestosa de la sintaxis de Python “famosa” y “obvia”: lo que tiene prioridad, el lambda o el for de la comprensión de listas.

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 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 ese no es el punto. El punto es ¿Por qué TF es esta expresión ambigua tan contraintuitiva? Y al final tengo un caso idiota para ti, así que no descartes mi respuesta demasiado pronto (lo 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 diferente copias de la función de cuadratura, consulte:

>>> [lambda x: x*x for _ in range(3)]
[ at 0x00000000023AD438>,  at 0x00000000023AD4A8>,  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
[ at 0x00000000023AD2E8>,  at 0x00000000023AD2E8>,  at 0x00000000023AD2E8>]

¿Ver? 3 Tiempo lo mismo lambda.

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

Dejo fuera 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 lo que encuentro más lógico, esto sería un list de 1 elemento – a generator devolviendo los valores. Simplemente no es el caso, el idioma no funciona de esta manera.

PERO Y si…

¿Qué pasa si NO eclipsa el for variable, Y úselo en su lambda¿¿¿s???

Bueno, entonces pasa la 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 comprensión de la lista son un cierre sobre el alcance de esta comprensión. A léxico cierre, por lo que se refieren al 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 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 me refiero, por ejemplo, el dis module), pero para una discusión independiente de Python-VM esto es suficiente. Hasta aquí la pregunta de la entrevista de trabajo.

Ahora, como hacer un list de lambdas multiplicadoras, 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

(También tenía la variable lambda externa = i, pero decidí que esta es la solución más clara: presenté 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 – el key es que se pueda acceder a él en el momento adecuado. La sección “Después” de arriba 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 una partial objeto, y tienen el “interior” (original) lambda tomarlo como un argumento (pasado proporcionado por el partial objeto 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 facilitarle las cosas al lector de código y pasar el factor por nombre (como un 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 "", line 1, in 
TypeError: () got multiple values for argument 'coef'

¿QUÉ?

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

Espera … ¿Estamos cambiando el número de argumentos en 1, y vamos de “demasiados” a “muy pocos”?

Bueno, no es un verdadero WAT, cuando pasamos coef para partial de esta manera, se convierte en un argumento de palabra clave, por lo que debe ir después de la posición x argumento, así:

Después de las 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 a la lambda anidada, pero a cada una la suya …

Editar 2020-08-18:

Gracias al comentarista dasWesen, descubrí que estas cosas están cubiertas en la documentación de Python: https://docs.python.org/3.4/faq/programming.html#why-do-lambdas-defined-in-a-loop- con-valores-diferentes-todos-devuelven-el-mismo-resultado: se trata de bucles en lugar de listas por comprensión, pero la idea es la misma: acceso a variables globales o no locales en la función lambda. Incluso hay una solución: usar valores de argumento predeterminados (como para cualquier función):

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

De esta manera, el valor de coef está vinculado al valor de i en el momento de la definición de la función (ver la charla de James Powell “De arriba a abajo, de izquierda a derecha”, que también explica por qué se evitan los valores predeterminados mutables).

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

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

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)


Tags : /

Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *