lambda 函数及其参数的作用域?

108 投票
9 回答
46386 浏览
提问于 2025-04-15 11:59

我需要一个回调函数,这个函数几乎在一系列图形用户界面(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()

但这也有同样的问题。

9 个回答

6

当然,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(m) [把x替换为m],行为是一样的。我用不同的名字是为了更清楚 :)

你可能会发现Andrej Bauer也遇到了类似的lambda问题。那个博客有趣的地方在于评论区,你可以在那儿学到更多关于Python闭包的知识 :)

158

当你创建一个lambda表达式时,它并不会把它用到的外部变量的值复制一份。相反,它会保持对那个环境的引用,这样它就可以在需要的时候查找变量的值。这里只有一个变量m。在每次循环中,这个变量都会被重新赋值。循环结束后,变量m的值变成了'mi'。所以,当你之后运行你创建的那个函数时,它会在创建它的环境中查找m的值,而那个时候m的值已经是'mi'了。

解决这个问题的一个常见方法是,在创建lambda时,通过将m作为一个可选参数的默认值来捕获它的值。通常你会使用同样名字的参数,这样就不需要改动代码的主体部分:

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

这里的问题在于变量 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 时都会创建一次。

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

撰写回答