2010-12-13 56 views
5

J'ai le code python suivant qui génère une liste de fonctions anonymes:Création d'une liste de fonctions en python

basis = [ (lambda x: n*x) for n in [0, 1, 2] ]  
print basis[0](1) 

je l'aurais prévu qu'il soit équivalent à

basis = [ (lambda x: 0*x), (lambda x: 1*x), (lambda x: 2*x) ] 
print basis[0](1) 

Cependant, alors que le deuxième extrait imprime 0 qui est ce que je m'attendais, les premières impressions 2. Quel est le problème avec le premier extrait de code, et pourquoi ne se comporte-t-il pas comme prévu?

+0

question connexe: http://stackoverflow.com/q/139819/4279 – jfs

Répondre

8

Vous pouvez utiliser un paramètre par défaut pour créer une fermeture sur n

>>> basis = [ (lambda x,n=n: n*x) for n in [0, 1, 2] ]  
>>> print basis[0](1) 
0 
+3

Cette technique est fréquemment utilisée pour transmettre des méthodes en tant que fonctions de rappel. Cela ressemble à quelque chose comme 'register_event_handler (événement = evt, callback = lambda cbVar1, cbVar2, s = soi: s.handle_evt (cbVar1, cbVar2))'. –

2

Le problème est que dans le premier exemple, chaque lambda est lié au même n - en d'autres termes, il capture la variable, pas la valeur de la variable. Puisque n a la valeur 2 à la fin de la boucle, chaque lambda utilise la valeur 2 pour n.

Apparemment, vous pouvez utiliser les paramètres par défaut pour résoudre ce problème:

basis = [ (lambda x,n=n: n*x) for n in [0, 1, 2] ] 
print basis[0](1) 

Puisque les valeurs des paramètres par défaut sont des constantes, la n sur le côté droit de n=n sera évalué chaque fois dans la boucle pour vous donner une nouvelle valeur capturée.

+0

Des suggestions sur la façon de résoudre ce problème? – dzhelil

+1

celil: J'ai édité ma réponse pour proposer un correctif suggéré, mais ce n'est pas très élégant. – Gabe

+0

Les paramètres par défaut peuvent ne pas être élégants, mais ils sont beaucoup plus faciles à regarder qu'une évaluation partielle explicite comme je le faisais auparavant: 'basis = [(lambda n: (lambda x: n * x)) (n) pour n dans la gamme (3)] '. :/Le paramètre par défaut n'est pas une "constante" dans le sens d'être immuable, mais il est évalué et lié dans '.func_defaults' du lambda. Les paramètres de fermeture le font aussi, mais apparemment d'une manière plus magique ... –

3

Parce que c'est "passer par le nom".

C'est, lorsque le lambda est exécuté, il exécute n*x: x est lié à 1 (il est un paramètre), n est recherché dans l'environnement (il est maintenant). Ainsi, le résultat est 2.

+0

Cela n'explique pas pourquoi le même comportement est affiché lorsque nous utilisons une compréhension de générateur à la place, qui ne «fuit» pas «n» dans les «locals()». AFAICT, la valeur de 'n' est recherchée à partir de' .func_closure' de lambda, et il n'est pas du tout clair comment cela se réfère magiquement à tout 'n' dernier point référé quand' n' n'existe plus. –

0

Je veux aider à la compréhension du commentaire de Karl Knechtel (13 décembre '10 à 7 : 32). Le code suivant montre comment l'utilisation du générateur, la définition lambda d'origine donne le résultat escompté, mais il n'a pas l'aide liste ou tuple:

>>> #GENERATOR 
... basis = ((lambda x: n*x) for n in [0, 1, 2]) 
>>> print(type(basis)) 
<type 'generator'> 
>>> basis = ((lambda x: n*x) for n in [0, 1, 2]) 
>>> print([x(3) for x in basis]) 
[0, 3, 6] 
>>> #TUPLE 
... basis = tuple((lambda x: n*x) for n in [0, 1, 2]) 
>>> print(type(basis)) 
<type 'tuple'> 
>>> print([x(3) for x in basis]) 
[6, 6, 6] 
>>> #LIST 
... basis = list((lambda x: n*x) for n in [0, 1, 2]) 
>>> print(type(basis)) 
<type 'list'> 
>>> print([x(3) for x in basis]) 
[6, 6, 6] 
>>> #CORRECTED LIST 
... basis = list((lambda x, n=n: n*x) for n in [0, 1, 2]) 
>>> print(type(basis)) 
<type 'list'> 
>>> print([x(3) for x in basis]) 
[0, 3, 6]