lambda函数如何在python中引用它的参数?

Posted

技术标签:

【中文标题】lambda函数如何在python中引用它的参数?【英文标题】:How does a lambda function refer to its parameters in python? 【发布时间】:2011-07-08 14:09:12 【问题描述】:

我是 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 在不同的函数中具有不同的值。

所以我的问题是:

    lambda中的变量i是全局的还是局部的?

    python 是否与 javascript 中的“闭包”具有相同的概念?我的意思是这里的每个 lambda 是否都包含对 i 变量的引用,或者它们只是在每个中都包含 i 值的副本?

    如果我希望在这种情况下输出为[0, 1, .....9],我应该怎么做?

【问题讨论】:

【参考方案1】:

看起来有点乱,但是你可以通过这样的方式得到你想要的:

>>> 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 只绑定一次并连续接受每个值,让每个返回的函数都像 @ 987654323@ 是 9。上面的 hack 使用捕获的 y 值将i 的每个值显式传递到返回另一个 lambda 的 lambda。

【讨论】:

是的,上面的技巧很聪明。它使用额外的闭包创建本地副本。 实际上,闭包的工作方式与预期的一样(捕获变量,而不是创建闭包时的变量值)——JS 也这样做(尝试在循环中创建函数)。跨度> 好像 i 只被绑定一次,并且连续取每个值,... 如果是这样,i 的值不应该是@ 987654327@,因为它是xrange生成的第一个值? 其实python中所有的闭包都是这样工作的,简直太疯狂了。人们需要停止为蟒蛇辩护。 Python 太疯狂了。【参考方案2】:
    变量 i 是列表解析的本地变量,但它对 lambda 可用,因为 lambda 在其范围内。 是的,lambda 是闭包。变量绑定可能并不总是按您希望的方式工作,但它们是闭包。不过,您不应该过度依赖它们。 您只想循环遍历xrange(10)。您可以使用 lambdas 执行此操作(请参阅其他答案),但您不想这样做。 Lambda 应该非常谨慎地使用。

lambda 以这种方式运行的原因是因为对于每个循环 i 都会反弹。由于 i 不是 lambda 的本地变量,因此它也会发生变化,并且它保存的最后一个值是 9。所以你所做的就是 0 + 9 10 次。

【讨论】:

【参考方案3】:

i 是本地的,python 中确实有闭包。

我相信您的困惑是您将 fs 分配给相同功能的列表。

>>> fs = [lambda x: x + i for i in xrange(10)]
>>> fs
[<function <lambda> at 0x02C6E930>, <function <lambda> at 0x02C6E970>, <function <lambda> at 0x02C6E9B0>, <function <lambda> at 0x02C6E9F0>, <function <lambda> at 0x02C6EA30>, <function <lambda> at 0x02C6EA70>, <function <lambda> at 0x02C6EAB0>, <function <lambda> at 0x02C6EAF0>, <function <lambda> at 0x02C6EB30>, <function <lambda> at 0x02C6EB70>]
>>> fs[0](0)
9
>>> fs[0](100)
109
>>> fs[5](0)
9
>>> fs[5](100)
109

我认为返回列表的单个函数会更合适。

>>> fs3 = lambda x: [x + i for i in xrange(10)]
>>> fs3
<function <lambda> at 0x02C6EC70>
>>> fs3(0)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

【讨论】:

【参考方案4】:

您在这里遇到的问题是“早期绑定”和“后期绑定”之间的区别。

当 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 的优点是不会弄乱返回函数的签名,默认参数 hack 比在定义命名函数而不是使用 lambda 表达式时添加额外的闭包级别更快且可读性更高。

【讨论】:

是的,很高兴知道在 python 中有另一种 hack。 Greg 的技巧在 Javascript 中有对应关系,但你的技巧对我来说是新的。感谢您的精彩解释

以上是关于lambda函数如何在python中引用它的参数?的主要内容,如果未能解决你的问题,请参考以下文章

Python核心编程总结(三引用与文件操作)

新手入门,深入解析 python lambda表达式

python tkinter, 通过lambda表达式传递参数到按钮的点击事件函数

将 lambda 作为参数传递 - 通过引用或值?

Python lambda函数

Kotlin标准库函数 ② ( run 标准库函数 | run 函数传入 Lambda 表达式作为参数 | run 函数传入函数引用作为参数 )