Python中lambda函数如何引用其参数?

6 投票
4 回答
1186 浏览
提问于 2025-04-16 12:59

我刚开始学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在不同的函数中有不同的值。

所以我想问:

  1. 变量i是全局的还是局部的?

  2. Python有没有像JavaScript中的“闭包”这样的概念?我的意思是,这里的每个lambda是否都持有对i变量的引用,还是它们只是各自持有i的值的副本?

  3. 如果我想要输出是[0, 1, .....9],我该怎么做?

4 个回答

4
  1. 变量 i 是在列表推导式里面的局部变量,但它可以被 lambda 使用,因为 lambda 在它的作用范围内。
  2. 没错,lambda 是闭包。变量的绑定有时候可能不会像你想的那样工作,但它们确实是闭包。不过,你不应该过于依赖它们。
  3. 你只需要遍历 xrange(10)。你可以用 lambda 来实现这个(可以看看其他的回答),但其实不太推荐这样做。lambda 应该尽量少用。

lambda 之所以这样表现,是因为在每次循环中,i 都会被重新绑定。由于 i 不是 lambda 的局部变量,它也会改变,最后的值是 9。所以你实际上做的就是 0 + 9 这个操作重复了 10 次。

6

你遇到的问题是“早绑定”和“晚绑定”之间的区别。

当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表达式时。

10

看起来有点复杂,但你可以通过下面的方式来实现你想要的效果:

>>> 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 的值。

撰写回答