52 votes

Les lambdas d'une liste de compréhension retournent un lambda lorsqu'ils sont appelés.

J'essaye d'itérer le lambda func sur une liste comme dans test.py et je veux obtenir le résultat de l'appel de la lambda, et non l'objet fonction lui-même. Cependant, la sortie suivante m'a vraiment dérouté.

------test.py---------
#!/bin/env python
#coding: utf-8

a = [lambda: i for i in range(5)]
for i in a:
    print i()

--------output---------
<function <lambda> at 0x7f489e542e60>
<function <lambda> at 0x7f489e542ed8>
<function <lambda> at 0x7f489e542f50>
<function <lambda> at 0x7f489e54a050>
<function <lambda> at 0x7f489e54a0c8>

J'ai modifié le nom de la variable lors de l'impression du résultat de l'appel en t comme suit, et tout se passe bien. Je me demande ce qu'il en est de tout cela. ?

--------test.py(update)--------
a = [lambda: i for i in range(5)]
for t in a:
    print t()

-----------output-------------
4
4
4
4
4

46voto

niemmi Points 14365

Dans Python 2, la compréhension des listes fait "fuir" les variables vers la portée externe :

>>> [i for i in xrange(3)]
[0, 1, 2]
>>> i
2

Notez que le comportement est différent sur Python 3 :

>>> [i for i in range(3)]
[0, 1, 2]
>>> i
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'i' is not defined

Lorsque vous définissez un lambda, il est lié à une variable. i et non sa valeur actuelle comme le montre votre deuxième exemple. Maintenant, lorsque vous attribuez une nouvelle valeur à i le lambda retournera la valeur courante :

>>> a = [lambda: i for i in range(5)]
>>> a[0]()
4
>>> i = 'foobar'
>>> a[0]()
'foobar'

Puisque la valeur de i dans la boucle est la lambda elle-même, vous l'obtiendrez comme valeur de retour :

>>> i = a[0]
>>> i()
<function <lambda> at 0x01D689F0>
>>> i()()()()
<function <lambda> at 0x01D689F0>

UPDATE : Exemple sur Python 2.7 :

Python 2.7.6 (default, Jun 22 2015, 17:58:13) 
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> a = [lambda: i for i in range(5)]
>>> for i in a:
...     print i()
... 
<function <lambda> at 0x7f1eae7f15f0>
<function <lambda> at 0x7f1eae7f1668>
<function <lambda> at 0x7f1eae7f16e0>
<function <lambda> at 0x7f1eae7f1758>
<function <lambda> at 0x7f1eae7f17d0>

Idem sur Python 3.4 :

Python 3.4.3 (default, Oct 14 2015, 20:28:29) 
[GCC 4.8.4] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> a = [lambda: i for i in range(5)]
>>> for i in a:
...     print(i())
... 
4
4
4
4
4

Pour plus de détails sur le changement concernant la portée des variables avec la compréhension des listes, voir l'article de Guido intitulé blogpost de 2010 .

Nous avons également apporté un autre changement dans Python 3, afin d'améliorer l'équivalence entre les compréhensions de listes et les expressions de générateur. Dans Python 2, la compréhension de liste "laisse échapper" la variable de contrôle de la boucle dans la portée environnante :

x = 'before'
a = [x for x in 1, 2, 3]
print x # this prints '3', not 'before'

Cependant, dans Python 3, nous avons décidé de corriger le "sale petit secret" des compréhensions de listes en utilisant la même stratégie de mise en œuvre que pour les expressions génératrices. Ainsi, en Python 3, l'exemple ci-dessus (après modification pour utiliser print(x) :-) affichera 'before', prouvant que le 'x' dans la compréhension de liste fait temporairement de l'ombre au 'x' dans la portée environnante mais ne le remplace pas.

19voto

plamut Points 2269

Les fermetures en Python sont Liaison tardive ce qui signifie que chaque fonction lambda de la liste n'évaluera que la variable i lorsqu'il est invoqué, et pas lorsqu'il est défini. C'est pourquoi toutes les fonctions renvoient la même valeur, c'est-à-dire la dernière valeur de ì (qui est de 4).

Pour éviter cela, une technique consiste à lier la valeur de l'option i à un paramètre local nommé :

>>> a = [lambda i=i: i for i in range(5)]
>>> for t in a:
...   print t()
... 
0
1
2
3
4

Une autre option consiste à créer un fonction partielle et lier la valeur actuelle de i comme son paramètre :

>>> from functools import partial
>>> a = [partial(lambda x: x, i) for i in range(5)]
>>> for t in a:
...   print t()
... 
0
1
2
3
4

Editar: Désolé, j'ai mal lu la question au départ, car ce genre de questions concerne souvent la reliure tardive (merci). A bientôt pour le commentaire).

La deuxième raison de ce comportement est la fuite des variables de la compréhension de liste dans Python2, comme d'autres l'ont déjà expliqué. Lorsque l'on utilise i comme variable d'itération dans le for chaque fonction imprime la valeur actuelle de i (pour les raisons mentionnées ci-dessus), qui est simplement la fonction elle-même. Lorsque l'on utilise un nom différent (par ex. t ), les fonctions impriment la dernière valeur de i comme il l'était dans la boucle de compréhension de la liste, c'est-à-dire 4.

4voto

nigel222 Points 1900

lambda: i est une fonction anonyme sans arguments qui renvoie i. Vous générez donc une liste de fonctions anonymes, que vous pourrez plus tard (dans le deuxième exemple) lier au nom t et l'invoquer avec () . Notez que vous pouvez faire de même avec les fonctions non anonymes :

>>> def f():
...   return 42
... 
>>> name = f
>>> name
<function f at 0x7fed4d70fb90>
>>> name()
42

@plamut vient de répondre à l'autre partie implicite de la question, donc je ne le ferai pas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X