9

La siguiente prueba falla:¿Por qué los resultados del mapa() y la comprensión de la lista son diferentes?

#!/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] 
+3

Para aquellos que como yo leen la pregunta pero no notan ningún problema al principio: tenga en cuenta que '[- 1, -1] '! Esencialmente 'lambda i: ...' en un ciclo no captura el valor actual de i. –

+0

relacionado de Python Preguntas frecuentes: [¿Por qué las lambdas definidas en un bucle con diferentes valores devuelven el mismo resultado?] (Https://docs.python.org/3/faq/programming.html#why-do-lambdas-defined -in-a-loop-with-different-values-all-return-the-same-result) – jfs

Respuesta

9

Ellos son diferentes, ya que el valor de i tanto en la expresión generadora y los comp de la lista se evalúan perezosamente, es decir, cuando se invocan las funciones anónimas en f.
En ese momento, i está vinculado al último valor si t, que es -1.

Así que, básicamente, esto es lo que hace la lista por comprensión (lo mismo para el genexp):

x = [] 
i = 1 # 1. from t 
x.append(lambda: i) 
i = -1 # 2. from t 
x.append(lambda: i) 

Ahora los lambdas llevar alrededor de un cierre que hace referencia a i, pero i está obligado a -1 en ambos casos, porque ese es el último valor al que fue asignado.

Si desea asegurarse de que el lambda recibe el valor actual de i, hacer

f(*[lambda u=i: u for i in t]) 

De esta manera, se 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 listas de comprensión: esta última filtra la variable de bucle en el ámbito circundante.

+0

Las lambdas son malas porque no está claro qué es realmente el contexto de tiempo de ejecución. –

+4

@ S.Lott: Las funciones ordinarias en Python no son tan diferentes. 'def f(): return i' Usted no sabe lo que' i' realmente es independientemente de la función o se considera lambda. – jfs

+1

En Python3, ["las variables de control de bucle ya no se filtran en el ámbito circundante."] (Https://docs.python.org/3.0/whatsnew/3.0.html#changed-syntax) – unutbu

5

La lambda captura variables, no los valores, por lo tanto, el código

lambda : i 

siempre devuelve el valor de i es actualmente obligado en el cierre. Cuando se llama, este valor se ha establecido en -1.

para conseguir lo que quiere, que necesita para capturar la unión real en el momento de crear la lambda, por:

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

Expresión f = lambda: i es equivalente a:

def f(): 
    return i 

Expresión g = lambda i=i: i es equivalente a:

def g(i=i): 
    return i 

i es una free variable en el primer caso y es obligado a parámetro de la 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.

expresión generador es el alcance más cercano de encerramiento (donde i se define) para i nombre en la expresión lambda, por lo tanto i 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 está definido en ese bloque:

f(*map(lambda i: lambda: i, (1,-1))) # -> [1, -1] 
Cuestiones relacionadas