105

我需要一个与一系列 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()

但这也有同样的问题。

4

9 回答 9

147

创建 lambda 时,它不会复制它使用的封闭范围内的变量。它维护对环境的引用,以便以后可以查找变量的值。只有一个m。每次循环都会分配给它。在循环之后,变量m具有 value 'mi'。因此,当您实际运行稍后创建的函数时,它会在创建它的环境中查找 的值,届时该值m将具有 value 'mi'

此问题的一个常见且惯用的解决方案是m通过将 lambda 用作可选参数的默认参数来捕获创建 lambda 时的值。您通常使用同名的参数,因此您不必更改代码主体:

for m in ('do', 're', 'mi'):
    funcList.append(lambda m=m: callback(m))
于 2009-06-02T08:27:06.053 回答
86

这里的问题是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()
于 2009-06-02T08:14:33.270 回答
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)[用 m 替换 x],行为是相同的。为了清楚起见,我使用了不同的名称:)

您可能会发现Andrej Bauer遇到了类似的 lambda 问题。该博客上有趣的是评论,您将在其中了解有关 python 闭包的更多信息 :)

于 2009-06-02T08:27:52.760 回答
1

是的,这是范围的问题,它绑定到外部 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))
于 2009-06-02T08:25:41.987 回答
1

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用于绑定 to 的i当前j

每次lambda调用外部时,它都会创建一个内部实例,lambdaj绑定到ias的当前i

于 2017-07-13T23:42:33.123 回答
0

首先,您所看到的不是问题,与按引用或按值调用无关。

您定义的 lambda 语法没有参数,因此,您使用参数看到的范围m在 lambda 函数之外。这就是您看到这些结果的原因。

Lambda 语法,在您的示例中不是必需的,您宁愿使用简单的函数调用:

for m in ('do', 're', 'mi'):
    callback(m)

同样,您应该非常准确地了解您使用的 lambda 参数以及它们的范围开始和结束的确切位置。

作为旁注,关于参数传递。python中的参数总是对对象的引用。引用 Alex Martelli 的话:

术语问题可能是由于在 python 中,名称的值是对对象的引用。因此,您始终传递值(没有隐式复制),并且该值始终是引用。[...] 现在,如果您想为此命名,例如“按对象引用”、“按未复制的值”或其他任何名称,请成为我的客人。恕我直言,试图重用更普遍适用于“变量是框”的语言到“变量是便利贴标签”的语言的术语更容易混淆而不是帮助。

于 2009-06-02T08:20:56.863 回答
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
于 2009-06-02T08:22:27.827 回答
0

在 Python 中实际上没有经典意义上的变量,只有通过对适用对象的引用绑定的名称。甚至函数也是 Python 中的某种对象,而 lambdas 也不例外 :)

于 2009-06-02T08:37:56.733 回答
0

作为旁注,map尽管被一些著名的 Python 人物所鄙视,但它强制构建了一种防止这种陷阱的结构。

fs = map (lambda i: lambda: callback (i), ['do', 're', 'mi'])

注意:第一个lambda i在其他答案中就像工厂一样。

于 2012-09-07T09:26:57.183 回答