9

Le test échoue suivant:Pourquoi les résultats de la compréhension de la carte() et de la liste sont-ils différents?

#!/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 d'autres termes:

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

Pour ceux comme moi, lisez la question mais ne remarquez aucun problème au début: notez le '[- 1, -1] '! Essentiellement 'lambda i: ...' dans une boucle ne capture pas la valeur actuelle de i. –

+0

liés à Python FAQ: [Pourquoi les lambdas définis dans une boucle avec des valeurs différentes retournent-ils tous le même résultat?] (Https://docs.python.org/3/faq/programming.html#why-do-lambdas-defined -dans-une-boucle-avec-des-valeurs-differentes-tous-des-retours-le-meme-resultat) – jfs

Répondre

9

Ils sont différents, parce que la valeur de i à la fois l'expression du générateur et la liste comp est évaluée paresseusement, c'est-à-dire lorsque les fonctions anonymes sont invoquées dans f.
A ce moment-là, i est lié à la dernière valeur si t, soit -1.

Donc, fondamentalement, c'est ce que la compréhension de la liste fait (de même pour le Genexp):

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

Maintenant, les lambdas portent autour d'une fermeture qui fait référence i, mais i est lié à -1 dans les deux cas, parce que c'est la dernière valeur à laquelle il a été assigné.

Si vous voulez vous assurer que le lambda reçoit la valeur actuelle de i, faire

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

De cette façon, vous forcer l'évaluation des i au moment où la fermeture est créée.

Édition: Il existe une différence entre les expressions de générateur et les compréhensions de liste: ces dernières transfèrent la variable de boucle dans la portée environnante.

+0

Les lambda sont mal parce que le contexte d'exécution n'est pas clair. –

+4

@ S.Lott: Les fonctions ordinaires en Python ne sont pas si différentes. 'def f(): return i' Vous ne savez pas ce que' i' est vraiment quelle que soit la fonction ou lambda est considéré. – jfs

+1

Dans Python3, ["les variables de contrôle de boucle ne sont plus divulguées dans la portée environnante."] (Https://docs.python.org/3.0/whatsnew/3.0.html#changed-syntax) – unutbu

5

lambda capture variables, non valeurs, donc le code

lambda : i 

retournera toujours la valeur i est actuellement lié à la fermeture. Au moment où il est appelé, cette valeur a été définie sur -1.

obtenir ce que vous voulez, vous devez capturer la liaison réelle au moment où le lambda est créé, par:

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

Expression f = lambda: i est équivalent à:

def f(): 
    return i 

Expression g = lambda i=i: i est équivalent à:

def g(i=i): 
    return i 

i est un free variable dans le premier cas et il est lié au paramètre de fonction dans le second cas c'est-à-dire, c'est une variable locale dans ce cas. Les valeurs des paramètres par défaut sont évaluées au moment de la définition de la fonction.

expression du générateur est la portée de l'enceinte la plus proche (où i est défini) pour i nom dans l'expression lambda donc i est résolu en ce que le bloc:

f(*(lambda: i for i in (1, -1)) # -> [-1, -1] 

i est une variable locale du bloc lambda i: ..., par conséquent, l'objet auquel il se réfère est défini dans ce bloc:

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