Lorsque les instructions de fonction sont exécutés, ils sont liés à leur (lexicalement) renfermant la portée.
Dans votre extrait de code, les lambdas sont liés à la portée globale, car les suites for
ne sont pas exécutées comme une unité de portée indépendante en Python. À la fin de la boucle for
, le num
est lié dans la portée englobante. Démo:
for num in range(1, 6):
pass
assert num == 5 # num is now bound in the enclosing scope
Ainsi, lorsque vous liez identifiants dans la boucle for
vous manipulez en fait la portée englobante.
for num in range(1, 6):
spam = 12
assert num == 5 # num is now bound in the enclosing scope
assert spam == 12 # spam is also bound in the enclosing scope
deal Même chose pour la liste compréhensions:
[num for num in range(1, 6)]
assert num == 5
souffle de l'esprit, je sais. Anywho, avec nos nouvelles connaissances, nous pouvons déterminer que les lambdas que vous créez se réfèrent à l'identifiant (unique) num
lié dans la portée englobante. Cela devrait en faire plus de sens:
functions = []
for number in range(1, 6):
def fun():
return number
functions.append(fun)
assert all(fun() == 5 for fun in functions)
assert all(fun() is number for fun in functions)
Et voici la partie la plus fraîche qui démontre encore plus:
# Same as above -- commented out for emphasis.
#functions = []
#for number in range(1, 6):
# def fun():
# return number
# functions.append(fun)
#assert all(fun() == 5 for fun in functions)
#assert all(fun() is number for fun in functions)
number = 6 # Rebind 6 in the scope and see how it affects the results.
assert all(fun() == 6 for fun in functions)
donc la solution à cela, bien sûr, est de faire un nouveau champ d'application englobante pour chaque number
que vous souhaitez lier. En Python, vous pouvez créer de nouvelles étendues englobantes avec des modules, des classes et des fonctions. Il est courant d'utiliser une fonction uniquement pour créer une nouvelle portée englobante pour une autre fonction.
En Python, une fermeture est une fonction qui renvoie une autre fonction. Un peu comme un constructeur de fonction. Vérifiez get_fun
dans l'exemple suivant:
def get_fun(value):
""":return: A function that returns :param:`value`."""
def fun(): # Bound to get_fun's scope
return value
return fun
functions = []
for number in range(1, 6):
functions.append(get_fun(number))
assert [fun() for fun in functions] == range(1, 6)
Depuis get_fun
est une fonction, il obtient d'avoir son propre champ interne. Chaque fois que vous appelez get_fun
avec une valeur, une petite table est créée pour garder trace des liaisons à l'intérieur; c'est-à-dire qu'il est indiqué "Dans ce cadre, l'identificateur value
indique la chose qui a été transmise." Cette portée disparaît à la fin de l'exécution de la fonction, à moins qu'il y ait une raison pour que cela traîne.Si vous renvoyez une fonction à l'intérieur d'une étendue, c'est une bonne raison pour que certaines parties de la "table d'étendue" puissent se déplacer - cette fonction que vous renvoyez pourrait référencer des choses de cette table d'étendue quand vous l'appelez plus tard. Pour cette raison, lorsque fun
est créé au sein de get_fun
Python indique fun
sur la table de portée de get_fun
, que fun
conserve à portée de main lorsque cela est nécessaire.
Vous pouvez en savoir plus sur les détails et la terminologie technique (que j'ai adouci un peu) dans le Python docs on the execution model. Vous pouvez également regarder les parties de la portée englobante auxquelles une fonction fait référence avec print fun.__closure__
. Dans ce qui précède, nous voyons la référence au value
, qui se trouve être un entier:
# Same as before, commented out for emphasis.
#functions = []
#for number in range(1, 6):
# functions.append(get_fun(number))
#assert [fun() for fun in functions] == range(1, 6)
print functions[0].__closure__
# Produces: (<cell at 0x8dc30: int object at 0x1004188>,)
Cool. Est-ce que ce "hack" est documenté n'importe où? Y a-t-il une meilleure façon de faire des curry? Veuillez également ne plus mentionner votre lit rotatif. –
Je ne l'ai jamais vu explicitement mentionné dans aucune documentation. Il profite du fait que les valeurs par défaut des paramètres de fonction sont attribuées au moment de la création de la fonction, qui est documentée. – recursive
Cet exemple est presque identique à [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.4/faq/programming.html#why-do-lambdas- defined-in-a-loop-with-different-values-all-return-the-same-result) à partir des docs. – abarnert