aga*_*and 79 python lexical-closures
我需要一个回调函数,对于一系列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()
Run Code Online (Sandbox Code Playgroud)
此代码的输出是:
mi
mi
mi
do
re
mi
Run Code Online (Sandbox Code Playgroud)
我期望:
do
re
mi
do
re
mi
Run Code Online (Sandbox Code Playgroud)
为什么使用迭代器搞砸了?
我尝试过使用深度镜:
import copy
funcList=[]
for m in ('do', 're', 'mi'):
funcList.append(lambda: callback(copy.deepcopy(m)))
for f in funcList:
f()
Run Code Online (Sandbox Code Playgroud)
但这也存在同样的问题.
new*_*cct 121
创建lambda时,它不会复制它使用的封闭范围中的变量.它维护对环境的引用,以便以后可以查找变量的值.只有一个m.它通过循环每次都被分配.循环之后,变量m具有值'mi'.因此,当您实际运行稍后创建的函数时,它将查找m创建它的环境中的值,然后该值将具有值'mi'.
这个问题的一个常见和惯用的解决方案m是通过使用它作为可选参数的默认参数来捕获创建lambda时的值.您通常使用相同名称的参数,因此您不必更改代码的主体:
for m in ('do', 're', 'mi'):
funcList.append(lambda m=m: callback(m))
Run Code Online (Sandbox Code Playgroud)
lis*_*ine 71
这里的问题是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()
Run Code Online (Sandbox Code Playgroud)
在上面的例子中,lambda也使用周围的范围来查找m,但这次callback_factory是每个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()
Run Code Online (Sandbox Code Playgroud)
Python当然使用引用,但在这种情况下无关紧要.
当您定义lambda(或函数,因为这是完全相同的行为)时,它不会在运行时之前计算lambda表达式:
# defining that function is perfectly fine
def broken():
print undefined_var
broken() # but calling it will raise a NameError
Run Code Online (Sandbox Code Playgroud)
比你的lambda例子更令人惊讶:
i = 'bar'
def foo():
print i
foo() # bar
i = 'banana'
foo() # you would expect 'bar' here? well it prints 'banana'
Run Code Online (Sandbox Code Playgroud)
简而言之,思考动态:在解释之前没有任何评估,这就是为什么你的代码使用最新的m值.
当它在lambda执行中查找m时,m取自最顶层的范围,这意味着,正如其他人指出的那样; 你可以通过添加另一个范围来规避这个问题:
def factory(x):
return lambda: callback(x)
for m in ('do', 're', 'mi'):
funcList.append(factory(m))
Run Code Online (Sandbox Code Playgroud)
这里,当调用lambda时,它会在lambda的定义范围中查找x.这个x是工厂正文中定义的局部变量.因此,lambda执行时使用的值将是在调用factory期间作为参数传递的值.而doremi!
作为一个注释,我可以将工厂定义为工厂(m)[用x替换x],行为是相同的.为清晰起见,我使用了不同的名称:)
你可能会发现Andrej Bauer有类似的lambda问题.这个博客上有趣的是评论,你将在那里了解更多关于python封闭的信息:)
| 归档时间: |
|
| 查看次数: |
31686 次 |
| 最近记录: |