Ce n' (lambda) les fermetures de fonction de capture?
Récemment, j'ai commencé à jouer avec Python et je suis autour de quelque chose de particulier dans la façon dont les fermetures de travail. Considérons le code suivant:
adders=[0,1,2,3]
for i in [0,1,2,3]:
adders[i]=lambda a: i+a
print adders[1](3)
Il construit un tableau simple de fonctions que de prendre une seule entrée et de retour que l'entrée ajoutée par un nombre. Les fonctions sont construits dans for
boucle où l'itérateur i
s'exécute à partir de 0
à 3
. Pour chacun de ces numéros, un lambda
fonction est créée, qui capte i
et l'ajoute à l'entrée de la fonction. La dernière ligne appelle la deuxième lambda
fonction avec 3
en tant que paramètre. À ma grande surprise, la sortie a été 6
.
Je m'attendais à un 4
. Mon raisonnement était: en Python, tout est objet et ainsi, chaque variable est essentiel qu'un pointeur vers elle. Lors de la création de la lambda
fermetures pour i
, je m'attendais à stocker un pointeur vers l'objet integer actuellement pointée par i
. Cela signifie que lorsque i
assigné un nouvel objet integer il ne faut pas en effet le bouclage. Malheureusement, l'inspection de l' adders
tableau à l'intérieur d'un débogueur montre qu'il n'. Tous les lambda
, référez-vous à la dernière valeur de i
, 3
, qui résultats dans adders[1](3)
retour 6
.
Qui me font me demander sur les éléments suivants:
- Que font les fermetures de saisir exactement?
- Quelle est la façon la plus élégante de convaincre le
lambda
fonctions de capture de la valeur actuelle dei
d'une manière qui ne sera pas affectée lorsquei
modifie sa valeur?
Comment
i
quitter l'espace de noms?Eh bien, j'allais dire que
print i
ne fonctionne pas après la boucle. Mais je l'ai testé pour moi et maintenant je vois ce que tu veux dire - il ne fonctionne pas. Je n'avais aucune idée que les variables de boucle persisté après le corps de la boucle en python.Ouais, c'est ce que je voulais dire. De même pour
if
, with
, try
etc.C'est dans l'officiel de Python FAQ, en vertu de la Pourquoi ne lambdas défini en boucle, avec des valeurs différentes, tous retournent le même résultat?, avec à la fois une explication et la solution de contournement.
OriginalL'auteur Boaz | 2010-02-19
Vous devez vous connecter pour publier un commentaire.
Votre deuxième question a été posée, mais comme pour votre premier:
Portée en Python est
dynamiquelexicale. Une fermeture de toujours vous souvenir du nom et de la portée de la variable, et non pas l'objet, il est pointant vers. Depuis toutes les fonctions dans votre exemple, sont créés dans le même champ d'application et d'utiliser le même nom de variable, ils se réfèrent toujours à la même variable.EDIT: Quant à votre autre question de savoir comment remédier à cela, il y a deux façons qui viennent à l'esprit:
La plus concise, mais ne sont pas strictement équivalentes façon, c'est le celui qui est recommandé par Adrien Plisson. Créer un lambda avec un argument supplémentaire, et définissez l'argument supplémentaire de la valeur par défaut de l'objet que l'on veut préservé.
Un peu plus verbeux, mais moins hacky serait de créer un nouveau champ d'application chaque fois que vous créez le lambda:
Le champ d'application ici est créé à l'aide d'une nouvelle fonction (un lambda, par souci de concision), qui se lie son argument, et le dépassement de la valeur que vous souhaitez lier à l'argument. Dans le code réel, même si, vous allez probablement avoir une fonction ordinaire au lieu de le lambda de créer le nouveau champ d'application:
Python est statique de cadrage, pas de dynamique, portée.. c'est juste que toutes les variables sont des références, de sorte que lorsque vous définissez une variable à un nouvel objet, la variable elle-même (la référence) a le même emplacement, mais il indique quelque chose d'autre. la même chose se produit dans le Schéma si vous
set!
. voir ici pour ce dynamique de la portée est vraiment: voidspace.org.royaume-uni/python/articles/code_blocks.shtml .Option 2 ressemble à ce que les langages fonctionnels pourrait appeler un "Curry de fonction."
OriginalL'auteur Max Shawabkeh
vous pouvez forcer la saisie d'une variable à l'aide d'un argument avec une valeur par défaut:
l'idée est de déclarer un paramètre (savamment nommé
i
) et lui donner une valeur par défaut de la variable que vous voulez capturer (la valeur dei
)+1 aussi parce que c'est la solution approuvée par l'officiel de la FAQ.
C'est incroyable. La valeur par défaut de Python comportement, cependant, ne l'est pas.
OriginalL'auteur Adrien Plisson
Pour l'exhaustivité une autre réponse à votre deuxième question: Vous pouvez utiliser partielle dans le functools module.
Avec l'importation d'ajouter de l'opérateur comme Chris Lutz proposé l'exemple devient:
OriginalL'auteur Joma
Considérons le code suivant:
Je pense que la plupart des gens ne trouverez pas cette confusion. C'est le comportement attendu.
Alors, pourquoi les gens pensent qu'il serait différente quand il est fait dans une boucle? Je sais que j'ai fait cette erreur moi-même, mais je ne sais pas pourquoi. C'est la boucle? Ou peut-être le lambda?
Après tout, la boucle est juste une version plus courte de:
Cette réponse est la bonne, car elle explique pourquoi le même
i
variable est accessible pour chaque fonction lambda.OriginalL'auteur truppo
En réponse à votre deuxième question, la façon la plus élégante de le faire serait d'utiliser une fonction qui prend deux paramètres au lieu d'un tableau:
Cependant, l'utilisation de lambda ici est un peu idiot. Python nous donne la
operator
module, qui fournit une interface fonctionnelle pour les opérateurs de base. Le lambda ci-dessus a une surcharge inutile juste pour appeler l'opérateur d'addition:Je comprends que vous soyez de jouer, d'essayer d'explorer la langue, mais je ne peux pas imaginer une situation, je voudrais utiliser un tableau de fonctions où Python est portée étrangeté obtenir de la manière.
Si vous le vouliez, vous pourriez écrire une petite classe qui utilise votre tableau d'indexation de la syntaxe:
OriginalL'auteur Chris Lutz
Voici un nouvel exemple qui met en évidence la structure des données et du contenu d'une fermeture, pour aider à clarifier quand la enfermant contexte est "sauvée".
Qu'est-ce que par une fermeture?
Notamment, my_str n'est pas en f1 fermeture.
Ce f2 de fermeture?
Avis (à partir de la mémoire d'adresses) que les deux fermetures contiennent les mêmes objets. Ainsi, vous pouvez commencer de penser à la fonction lambda comme ayant une référence à la portée. Cependant, my_str n'est pas dans la fermeture pour f_1 ou f_2, et je n'est pas la fermeture de f_3 (non représenté), ce qui suggère la fermeture des objets eux-mêmes sont des objets distincts.
Sont la fermeture des objets eux-mêmes le même objet?
int object at [address X]>
m'a fait penser à la fermeture de stockage [adresse X] AKA une référence. Cependant, [adresse X] va changer si la variable est réaffecté après le lambda déclaration.OriginalL'auteur Jeff