340 votes

Ce qui saisissent les fermetures de fonction (lambda) en Python ?

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 en boucle où l'itérateur i va de 0 à 3. Pour chacun de ces numéro un lambda d'une fonction est créée, qui recueille des je et ajoute à l'entrée de la fonction. La dernière ligne appelle la deuxième fonction lambda 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 du lambda des fermetures pour - je, je m'attendais à stocker un pointeur vers l'objet integer actuellement pointée par je. Cela signifie que, quand j'ai assigné un nouvel objet integer il ne faut pas en effet le bouclage. Malheureusement, l'inspection de l'additionneurs tableau à l'intérieur d'un débogueur montre qu'il n'. Tous les lambda fonctions, reportez-vous à la dernière valeur de i, 3, ce qui entraîne adders1 retour 6.

Ce qui m'a le suivant:

  • ce qui ne les fermetures de saisir exactement?
  • Quelle est la façon la plus élégante de convaincre que les lambda fonctions de capture de la valeur actuelle de i et d'une manière qui ne sera pas affectée lorsque je change la valeur.

278voto

Adrien Plisson Points 9750

vous pouvez forcer la capture d’une variable à l’aide d’un argument avec une valeur par défaut :

l’idée est de déclarer un paramètre (habilement nommé ) et lui donner une valeur par défaut de la variable que vous souhaitez capturer (la valeur de )

202voto

Max Shawabkeh Points 19030

Votre deuxième question a été posée, mais comme pour votre premier:

quelle est la fermeture de saisir exactement?

Portée en Python est dynamique et lexicale. 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:

  1. La plus concise, mais ne sont pas strictement équivalent moyen est 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 vous souhaitez préserver.

  2. Un peu plus verbeux, mais moins hacky serait de créer un nouveau champ d'application chaque fois que vous créez le lambda:

    >>> adders = [0,1,2,3]
    >>> for i in [0,1,2,3]:
    ...     adders[i] = (lambda b: lambda a: b + a)(i)
    ...     
    >>> adders[1](3)
    4
    >>> adders[2](3)
    5
    

    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:

    def createAdder(x):
        return lambda y: y + x
    adders = [createAdder(i) for i in range(4)]
    

48voto

Joma Points 393

Pour être complet 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:

from functools import partial
from operator import add   # add(a, b) -- Same as a + b.

adders = [0,1,2,3]
for i in [0,1,2,3]:
   # store callable object with first argument given as (current) i
   adders[i] = partial(add, i) 

print adders[1](3)

32voto

truppo Points 10346

Considérons le code suivant:

x = "foo"

def print_x():
    print x

x = "bar"

print_x() # Outputs "bar"

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:

adders= [0,1,2,3]
i = 0
adders[i] = lambda a: i+a
i = 1
adders[i] = lambda a: i+a
i = 2
adders[i] = lambda a: i+a
i = 3
adders[i] = lambda a: i+a

2voto

Chris Lutz Points 34157

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:

add = lambda a, b: a + b
add(1, 3)

Cependant, l'utilisation de lambda ici est un peu idiot. Python nous donne l' 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:

from operator import add
add(1, 3)

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:

class Adders(object):
    def __getitem__(self, item):
        return lambda a: a + item

adders = Adders()
adders[1](3)

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