python - recorrer - ¿Por qué los resultados del mapa() y la comprensión de la lista son diferentes?
lista dentro de un diccionario python (3)
La siguiente prueba falla:
#!/usr/bin/env python
def f(*args):
"""
>>> t = 1, -1
>>> f(*map(lambda i: lambda: i, t))
[1, -1]
>>> f(*(lambda: i for i in t)) # -> [-1, -1]
[1, -1]
>>> f(*[lambda: i for i in t]) # -> [-1, -1]
[1, -1]
"""
alist = [a() for a in args]
print(alist)
if __name__ == ''__main__'':
import doctest; doctest.testmod()
En otras palabras:
>>> t = 1, -1
>>> args = []
>>> for i in t:
... args.append(lambda: i)
...
>>> map(lambda a: a(), args)
[-1, -1]
>>> args = []
>>> for i in t:
... args.append((lambda i: lambda: i)(i))
...
>>> map(lambda a: a(), args)
[1, -1]
>>> args = []
>>> for i in t:
... args.append(lambda i=i: i)
...
>>> map(lambda a: a(), args)
[1, -1]
Expresión f = lambda: i
es equivalente a:
def f():
return i
La expresión g = lambda i=i: i
es equivalente a:
def g(i=i):
return i
i
es una variable libre en el primer caso y está vinculada al parámetro de función en el segundo caso, es decir, es una variable local en ese caso. Los valores para los parámetros predeterminados se evalúan en el momento de la definición de la función.
La expresión de generador es el ámbito adjunto más cercano (donde i
se define) para el nombre en la expresión lambda
, por lo tanto, se resuelve en ese bloque:
f(*(lambda: i for i in (1, -1)) # -> [-1, -1]
i
es una variable local del bloque lambda i: ...
, por lo tanto, el objeto al que se refiere se define en ese bloque:
f(*map(lambda i: lambda: i, (1,-1))) # -> [1, -1]
La lambda captura variables, no valores, de ahí el código
lambda : i
siempre devolverá el valor al que estoy actualmente vinculado en el cierre. Cuando se llama, este valor se ha establecido en -1.
Para obtener lo que desea, tendrá que capturar el enlace real en el momento en que se crea el lambda, de la siguiente manera:
>>> f(*(lambda i=i: i for i in t)) # -> [-1, -1]
[1, -1]
>>> f(*[lambda i=i: i for i in t]) # -> [-1, -1]
[1, -1]
Son diferentes, porque el valor de i
tanto en la expresión del generador como en la lista se evalúa de forma diferida, es decir, cuando se invocan las funciones anónimas en f
.
En ese momento, i
está ligado al último valor si t
, que es -1.
Así que, básicamente, esto es lo que hace la lista de comprensión (del mismo modo para el genexp):
x = []
i = 1 # 1. from t
x.append(lambda: i)
i = -1 # 2. from t
x.append(lambda: i)
Ahora las lambdas llevan un cierre que hace referencia a i
, pero i
está limitado a -1 en ambos casos, porque ese es el último valor al que se le asignó.
Si desea asegurarse de que la lambda recibe el valor actual de i
, haga
f(*[lambda u=i: u for i in t])
De esta manera, fuerza la evaluación de i
en el momento en que se crea el cierre.
Editar : Hay una diferencia entre las expresiones del generador y las comprensiones de la lista: esta última filtra la variable de bucle dentro del alcance circundante.