J'ai besoin d'une fonction de rappel qui est presque exactement la même pour une série d'événements d'interface graphique. La fonction se comportera légèrement différemment selon l'événement qui l'a appelée. Cela me semble être un cas simple, mais je ne peux pas comprendre ce comportement étrange des fonctions lambda.
J'ai donc le code simplifié suivant ci-dessous:
def callback(msg):
print msg
#creating a list of function handles with an iterator
funcList=[]
for m in ('do', 're', 'mi'):
funcList.append(lambda: callback(m))
for f in funcList:
f()
#create one at a time
funcList=[]
funcList.append(lambda: callback('do'))
funcList.append(lambda: callback('re'))
funcList.append(lambda: callback('mi'))
for f in funcList:
f()
La sortie de ce code est:
mi
mi
mi
do
re
mi
J'esperais:
do
re
mi
do
re
mi
Pourquoi l'utilisation d'un itérateur a-t-il gâché les choses?
J'ai essayé d'utiliser une copie profonde:
import copy
funcList=[]
for m in ('do', 're', 'mi'):
funcList.append(lambda: callback(copy.deepcopy(m)))
for f in funcList:
f()
Mais cela pose le même problème.
python
lexical-closures
agartland
la source
la source
Réponses:
Le problème ici est que la
m
variable (une référence) est prise dans la portée environnante. Seuls les paramètres sont conservés dans la portée lambda.Pour résoudre ce problème, vous devez créer une autre portée pour lambda:
def callback(msg): print msg def callback_factory(m): return lambda: callback(m) funcList=[] for m in ('do', 're', 'mi'): funcList.append(callback_factory(m)) for f in funcList: f()
Dans l'exemple ci-dessus, lambda utilise également la portée environnante pour rechercher
m
, mais cette fois, c'est lacallback_factory
portée qui est créée une fois parcallback_factory
appel.Ou avec functools.partial :
from functools import partial def callback(msg): print msg funcList=[partial(callback, m) for m in ('do', 're', 'mi')] for f in funcList: f()
la source
Lorsqu'un lambda est créé, il ne fait pas de copie des variables dans la portée englobante qu'il utilise. Il conserve une référence à l'environnement afin de pouvoir rechercher ultérieurement la valeur de la variable. Il n'y en a qu'un
m
. Il est assigné à chaque fois dans la boucle. Après la boucle, la variablem
a une valeur'mi'
. Ainsi, lorsque vous exécutez réellement la fonction que vous avez créée plus tard, elle recherchera la valeur dem
dans l'environnement qui l'a créée, qui aura alors de la valeur'mi'
.Une solution courante et idiomatique à ce problème consiste à capturer la valeur de
m
au moment où le lambda est créé en l'utilisant comme argument par défaut d'un paramètre facultatif. Vous utilisez généralement un paramètre du même nom pour ne pas avoir à modifier le corps du code:for m in ('do', 're', 'mi'): funcList.append(lambda m=m: callback(m))
la source
Python utilise bien sûr des références, mais cela n'a pas d'importance dans ce contexte.
Lorsque vous définissez un lambda (ou une fonction, puisqu'il s'agit exactement du même comportement), il n'évalue pas l'expression lambda avant l'exécution:
# defining that function is perfectly fine def broken(): print undefined_var broken() # but calling it will raise a NameError
Encore plus surprenant que votre exemple lambda:
i = 'bar' def foo(): print i foo() # bar i = 'banana' foo() # you would expect 'bar' here? well it prints 'banana'
Bref, pensez dynamique: rien n'est évalué avant interprétation, c'est pourquoi votre code utilise la dernière valeur de m.
Lorsqu'il recherche m dans l'exécution lambda, m est pris dans la portée la plus élevée, ce qui signifie que, comme d'autres l'ont souligné; vous pouvez contourner ce problème en ajoutant une autre portée:
def factory(x): return lambda: callback(x) for m in ('do', 're', 'mi'): funcList.append(factory(m))
Ici, lorsque le lambda est appelé, il recherche dans la portée de la définition lambda un x. Ce x est une variable locale définie dans le corps de l'usine. Pour cette raison, la valeur utilisée lors de l'exécution lambda sera la valeur qui a été transmise en tant que paramètre lors de l'appel à l'usine. Et doremi!
Pour mémoire, j'aurais pu définir factory comme factory (m) [remplacer x par m], le comportement est le même. J'ai utilisé un nom différent pour plus de clarté :)
Vous constaterez peut-être qu'Andrej Bauer a des problèmes lambda similaires. Ce qui est intéressant sur ce blog, ce sont les commentaires, où vous en apprendrez plus sur la fermeture de python :)
la source
Pas directement lié au problème en question, mais néanmoins un élément de sagesse inestimable: les objets Python de Fredrik Lundh.
la source
Oui, c'est un problème de portée, il se lie au m extérieur, que vous utilisiez un lambda ou une fonction locale. À la place, utilisez un foncteur:
class Func1(object): def __init__(self, callback, message): self.callback = callback self.message = message def __call__(self): return self.callback(self.message) funcList.append(Func1(callback, m))
la source
la soluiton à lambda est plus lambda
In [0]: funcs = [(lambda j: (lambda: j))(i) for i in ('do', 're', 'mi')] In [1]: funcs Out[1]: [<function __main__.<lambda>>, <function __main__.<lambda>>, <function __main__.<lambda>>] In [2]: [f() for f in funcs] Out[2]: ['do', 're', 'mi']
l'externe
lambda
est utilisé pour lier la valeur actuelle dei
àj
auchaque fois que l'extérieur
lambda
est appelée , elle fait une instance de l'intérieurlambda
avecj
lié à la valeur courante dei
quei
la valeur de »la source
Premièrement, ce que vous voyez n'est pas un problème et n'est pas lié à l'appel par référence ou par valeur.
La syntaxe lambda que vous avez définie n'a pas de paramètres et, en tant que telle, la portée que vous voyez avec le paramètre
m
est externe à la fonction lambda. C'est pourquoi vous voyez ces résultats.La syntaxe Lambda, dans votre exemple n'est pas nécessaire, et vous préférez utiliser un simple appel de fonction:
for m in ('do', 're', 'mi'): callback(m)
Encore une fois, vous devez être très précis sur les paramètres lambda que vous utilisez et où exactement leur portée commence et se termine.
En remarque, concernant le passage de paramètres. Les paramètres en python sont toujours des références à des objets. Pour citer Alex Martelli:
la source
La variable
m
est capturée, donc votre expression lambda voit toujours sa valeur «actuelle».Si vous avez besoin de capturer efficacement la valeur à un moment donné, write a function prend la valeur souhaitée comme paramètre et renvoie une expression lambda. À ce stade, le lambda capturera la valeur du paramètre , qui ne changera pas lorsque vous appelez la fonction plusieurs fois:
def callback(msg): print msg def createCallback(msg): return lambda: callback(msg) #creating a list of function handles with an iterator funcList=[] for m in ('do', 're', 'mi'): funcList.append(createCallback(m)) for f in funcList: f()
Production:
la source
il n'y a en fait aucune variable au sens classique du terme en Python, juste des noms qui ont été liés par des références à l'objet applicable. Même les fonctions sont une sorte d'objet en Python, et les lambdas ne font pas d'exception à la règle :)
la source
En passant,
map
bien que méprisé par une figure bien connue de Python, force une construction qui empêche cet écueil.fs = map (lambda i: lambda: callback (i), ['do', 're', 'mi'])
NB: le premier
lambda i
agit comme l'usine dans d'autres réponses.la source