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()
但是这也有同样的问题。
这里的问题是来自周围范围的m
variables(一个引用)。 只有参数在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
的值