我需要一个回调函数,对于一系列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
为什么使用迭代器搞砸了?
我尝试过使用深度镜:
import copy
funcList=[]
for m in ('do', 're', 'mi'):
funcList.append(lambda: callback(copy.deepcopy(m)))
for f in funcList:
f()
但这也有同样的问题。
答案 0 :(得分:116)
创建lambda时,它不会复制它使用的封闭范围中的变量。它维护对环境的引用,以便以后可以查找变量的值。只有一个m
。它通过循环每次都被分配。循环之后,变量m
的值为'mi'
。因此,当您实际运行稍后创建的函数时,它将在创建它的环境中查找m
的值,然后它将具有值'mi'
。
此问题的一个常见且惯用的解决方案是通过将lambda用作可选参数的默认参数来捕获创建lambda时m
的值。您通常使用相同名称的参数,因此您无需更改代码正文:
for m in ('do', 're', 'mi'):
funcList.append(lambda m=m: callback(m))
答案 1 :(得分:66)
这里的问题是从周围范围中取得的m
变量(引用)。
只有参数保存在lambda范围内。
要解决此问题,您必须为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()
在上面的例子中,lambda也使用周围的范围来查找m
,但是这个
时间callback_factory
范围每callback_factory
创建一次
调用
from functools import partial
def callback(msg):
print msg
funcList=[partial(callback, m) for m in ('do', 're', 'mi')]
for f in funcList:
f()
答案 2 :(得分:5)
Python当然使用引用,但在此上下文中无关紧要。
当你定义一个lambda(或一个函数,因为这是完全相同的行为)时,它不会在运行时之前计算lambda表达式:
# 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'
简而言之,思考动态:在解释之前没有评估任何内容,这就是为什么你的代码使用最新的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是工厂正文中定义的局部变量。因此,lambda执行时使用的值将是在调用factory期间作为参数传递的值。而doremi!
作为一个注释,我可以将工厂定义为工厂(m)[用x替换x],行为是相同的。为清楚起见,我使用了不同的名称:)
你可能会发现Andrej Bauer有类似的lambda问题。在博客上有趣的是评论,你将在那里了解更多关于python封闭的内容:)
答案 3 :(得分:1)
与手头的问题没有直接关系,但却是一段宝贵的智慧:Fredrik Lundh Python Objects。
答案 4 :(得分:0)
首先,您所看到的不是问题,与参考调用或按值无关。
您定义的lambda语法没有参数,因此,您在参数m
中看到的范围是lambda函数的外部。这就是您看到这些结果的原因。
Lambda语法,在您的示例中不是必需的,您宁愿使用简单的函数调用:
for m in ('do', 're', 'mi'):
callback(m)
同样,您应该非常准确地了解您正在使用的lambda参数以及它们的范围的开始和结束位置。
作为旁注,关于参数传递。 python中的参数始终是对象的引用。引用Alex Martelli的话:
术语问题可能是由于 事实上,在python中,它的价值 名称是对象的引用。 所以,你总是传递价值(没有 隐式复制),那个值是 总是一个参考。 [...]现在,如果你 想要为此命名,例如 “通过对象引用”,“通过未复制 价值“,或者其他什么,是我的客人。 试图重用术语 更普遍适用于语言 其中“变量是盒子”到a “变量是post-it的语言 标签“是,恕我直言,更容易混淆 而不是帮助。
答案 5 :(得分:0)
正在捕获变量m
,因此您的lambda表达式始终会看到其“当前”值。
如果您需要在某个时刻有效捕获值,请编写一个函数,将您想要的值作为参数,并返回一个lambda表达式。此时,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
答案 6 :(得分:0)
是的,这是范围问题,它与外部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))
答案 7 :(得分:0)
在Python中,经典意义上实际上没有变量,只是通过对适用对象的引用绑定的名称。甚至函数都是Python中的某种对象,而lambdas也没有例外规则:)
答案 8 :(得分:0)
作为旁注,map
尽管被一些众所周知的Python人物所鄙视,却强制建立一个可以防止这种陷阱的结构。
fs = map (lambda i: lambda: callback (i), ['do', 're', 'mi'])
注意:第一个lambda i
在其他答案中就像工厂一样。
答案 9 :(得分:0)
soluiton to lambda更多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
的实例与j
绑定到当前值i
为i
' s值