Python中lambda函数如何引用其参数?
我刚开始学Python,任务其实很简单——我需要一个可以批量处理的函数列表。所以我试着做了一些例子,比如:
fs = [lambda x: x + i for i in xrange(10)]
结果让我很惊讶,当我调用
[f(0) for f in fs]
时,得到了像[9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
这样的结果。这不是我预期的,因为我希望变量i
在不同的函数中有不同的值。
所以我想问:
变量
i
是全局的还是局部的?Python有没有像JavaScript中的“闭包”这样的概念?我的意思是,这里的每个lambda是否都持有对
i
变量的引用,还是它们只是各自持有i
的值的副本?如果我想要输出是
[0, 1, .....9]
,我该怎么做?
4 个回答
- 变量
i
是在列表推导式里面的局部变量,但它可以被 lambda 使用,因为 lambda 在它的作用范围内。 - 没错,lambda 是闭包。变量的绑定有时候可能不会像你想的那样工作,但它们确实是闭包。不过,你不应该过于依赖它们。
- 你只需要遍历
xrange(10)
。你可以用 lambda 来实现这个(可以看看其他的回答),但其实不太推荐这样做。lambda 应该尽量少用。
lambda 之所以这样表现,是因为在每次循环中,i
都会被重新绑定。由于 i
不是 lambda 的局部变量,它也会改变,最后的值是 9。所以你实际上做的就是 0 + 9
这个操作重复了 10 次。
你遇到的问题是“早绑定”和“晚绑定”之间的区别。
当Python从外部范围查找一个变量(在这个例子中是i
)时,它使用的是晚绑定。这意味着它在调用函数的时候查看那个变量的值,而不是在定义函数的时候查看。
所以,在你的示例代码中,所有10个lambda函数都看到循环过程中i
变量最终被赋值为9
。
Greg的回答展示了一种强制早绑定的方法(也就是创建一个额外的闭包,并在循环内部立即调用它)。
另一种常用的强制早绑定的方法是“默认参数技巧”,它在函数定义时将变量绑定为默认参数:
>>> fs = [(lambda x, _i=i: x + _i) for i in xrange(10)]
>>> [f(0) for f in fs]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
这两种方法都可以。Greg的方法的好处是不会影响返回函数的签名,而默认参数技巧更快,并且比在定义命名函数时添加额外的闭包层要更易读,尤其是当你使用lambda表达式时。
看起来有点复杂,但你可以通过下面的方式来实现你想要的效果:
>>> fs = [(lambda y: lambda x: x + y)(i) for i in xrange(10)]
>>> [f(0) for f in fs]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
通常,Python 支持类似于你在 JavaScript 中见过的“闭包”概念。不过,在这个特定的情况下,当你在列表推导式中使用 lambda 表达式时,似乎 i
只被绑定了一次,并且依次取每个值,这导致每个返回的函数都像是 i
的值是 9。上面的技巧明确地将 i
的每个值传递给一个 lambda 函数,这个 lambda 函数又返回另一个 lambda 函数,并使用捕获到的 y
的值。