lambda函数的范围及其参数?

我需要一个callback函数,对于一系列gui事件几乎完全相同。 根据哪个事件调用它,函数的行为会稍有不同。 看起来像一个简单的例子,但我无法弄清这个lambda函数的怪异行为。

所以我有下面的简化代码:

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() 

这个代码的输出是:

 mi mi mi do re mi 

我期望:

 do re mi do re mi 

为什么使用迭代器搞砸了?

我试过使用deepcopy:

 import copy funcList=[] for m in ('do', 're', 'mi'): funcList.append(lambda: callback(copy.deepcopy(m))) for f in funcList: f() 

但是这也有同样的问题。

这里的问题是来自周围范围的mvariables(一个引用)。 只有参数在lambda范围内。

为了解决这个问题,你必须为lambda创build另一个范围:

 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() 

在上面的示例中,lambda也使用超范围来查找m ,但是这次是每个callback_factory调用创build一次的callback_factory范围。

或者用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() 

当创build一个lambda时,它不会使用它所使用的封闭范围中的variables的副本。 它维护对环境的引用,以便稍后可以查找variables的值。 只有一m 它通过循环每次被分配。 循环之后,variablesm值为'mi' 。 所以当你实际运行后面创build的函数的时候,它会在创build它的环境中查找m的值,这个值就是'mi'

这个问题的一个常见和习惯的解决scheme是在创buildlambda时通过使用它作为可选参数的默认参数来捕获m的值。 您通常使用相同名称的参数,因此您不必更改代码的主体:

 for m in ('do', 're', 'mi'): funcList.append(lambda m=m: callback(m)) 

Python确实使用了引用,但在这种情况下并不重要。

当你定义一个lambda(或一个函数,因为这是完全相同的行为),它不会计算运行时之前的lambdaexpression式:

 # defining that function is perfectly fine def broken(): print undefined_var broken() # but calling it will raise a NameError 

比你的lambda例子更令人惊讶:

 i = 'bar' def foo(): print i foo() # bar i = 'banana' foo() # you would expect 'bar' here? well it prints 'banana' 

总之,思考dynamic:解释之前没有评估,这就是为什么你的代码使用m的最新值。

当它在lambda执行中查找m时,m取自最顶端的范围,也就是说,正如其他人指出的那样; 你可以通过添加另一个范围来绕过这个问题:

 def factory(x): return lambda: callback(x) for m in ('do', 're', 'mi'): funcList.append(factory(m)) 

在这里,当调用lambda时,它会在lambda的定义范围中寻找一个x。 这个x是一个在工厂中定义的局部variables。 因此,在执行lambda时使用的值将是在调用工厂期间作为parameter passing的值。 和doremi!

作为说明,我可以将工厂定义为工厂(m)[用x代替x],行为是一样的。 为了清晰,我使用了不同的名称:)

你可能会发现Andrej Bauer得到了类似的lambda问题。 那个博客上有趣的是评论,你将会在这里学到更多关于python的信息:)

与手头的问题没有直接关系,但却是一个非常宝贵的智慧:Fredrik Lundh的Python Objects 。

首先,你所看到的不是一个问题,也不涉及按引用或按价值。

您定义的lambda语法没有参数,因此您使用参数m看到的作用域是lambda函数的外部。 这就是为什么你看到这些结果。

在您的示例中,Lambda语法不是必需的,您宁愿使用简单的函数调用:

 for m in ('do', 're', 'mi'): callback(m) 

同样,你应该非常精确地知道你正在使用的lambda参数以及它们的作用域开始和结束的位置。

作为旁注,关于parameter passing。 python中的参数始终是对象的引用。 引用Alex Martelli的话:

术语问题可能是由于这样一个事实,即在python中,名称的值是对象的引用。 所以,你总是传递值(不隐式复制),而且这个值总是一个引用。 […]现在,如果你想为这个名称,如“通过对象引用”,“通过无形的价值”,或其他,作为我的客人。 试图重用更通用的术语,其中“variables是框”的语言,“variables是后贴标签”的语言,恕我直言,更容易混淆,而不是帮助。

variablesm被捕获,所以你的lambdaexpression式总是看到它的“当前”值。

如果您需要及时有效地捕获值,请编写一个函数,将您想要的值作为参数,并返回一个lambdaexpression式。 此时,lambda将捕获参数的值,当您多次调用该函数时,该值不会改变:

 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() 

输出:

 do re mi 

实际上在Python中没有经典意义上的variables,只是通过引用适用对象来绑定的名称。 即使函数是Python中的某种对象,lambda也不会违反规则:)

作为一个侧面说明, map虽然被一些着名的Python人物所鄙视,但却强制build筑物防止这种陷阱。

 fs = map (lambda i: lambda: callback (i), ['do', 're', 'mi']) 

注:第一个lambda i在其他答案像工厂行事。

是的,这是一个范围问题,它绑定到外部m,无论您是使用lambda或本地函数。 相反,使用一个仿函数:

 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)) 

对lambda的solunton是更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'] 

外部的lambda被用来绑定i的当前值到j的位置

每次调用外层lambda ,都会将内层lambda的实例绑定到i的当前值,因为i的值