lambda函数的范围及其参数?

时间:2009-06-02 08:07:04

标签: 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()

此代码的输出为:

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

但这也有同样的问题。

10 个答案:

答案 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创建一次 调用

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

答案 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绑定到当前值ii' s值