【问题标题】:How to generate a list of different lambda functions with list comprehension?如何使用列表理解生成不同 lambda 函数的列表?
【发布时间】:2015-07-02 14:13:28
【问题描述】:

这个问题是从涉及 Tkinter 按钮回调函数的原始应用程序中提炼出来的。这是说明行为的一行。

lambdas = [lambda: i for i in range(3)]

如果您尝试调用生成的 lambda 函数: lambdas[0]()lambdas[1]()lambdas[2]() 都返回 2。

期望的行为是让lambdas[0]() 返回 0、lambdas[1]() 返回 1、lambdas[2])() 返回 2。

我看到索引变量是通过引用解释的。问题是如何改写以按价值处理。

【问题讨论】:

  • [lambda i = i: i for i in range(3)]
  • 规范副本:stackoverflow.com/q/12423614/3001761“索引变量是通过引用解释的” - 不,这是一个后期绑定的东西。

标签: python python-2.7 lambda list-comprehension


【解决方案1】:

使用带默认值的参数将i当前值绑定到局部变量。当lambda 不带参数被调用时,局部变量i 被赋予默认值:

In [110]: lambdas = [lambda i=i: i for i in range(3)]

In [111]: for lam in lambdas:
   .....:       print(lam())
   .....: 
0
1
2

i 不是局部变量时,Python looks up its value in the enclosing scope。找到的值是在列表推导的 for 循环中获得的最后一个值 i。这就是为什么在没有带默认值的参数的情况下,每个 lambda 都会返回 2,因为在调用 lambda 时 for 循环已经完成。


解决此问题的另一种常用方法是使用闭包 --a function that can refer to environments that are no longer active例如外部函数的本地命名空间,即使在该函数返回后也是如此

def make_func(i):
    return lambda: i

lambdas = [make_func(i) for i in range(3)]
for lam in lambdas:
    print(lam())

打印

0
1
2

这是因为当lam() 被调用时,因为lambda 中的i 函数体不是局部变量,Python 在中查找i 的值 函数make_func 的封闭范围。它的本地命名空间仍然是 可访问关闭,lam,即使 make_func 已经 完全的。该本地命名空间中的值是传递给的值 make_func,令人高兴的是,i 的期望值。


作为already mentioned by mkrieger1, 另一种方法来创建一个已经提供了一些参数值的新函数 是使用functools.partial:

lambdas = [functools.partial(lambda x: x, i) for i in range(3)]

【讨论】:

  • 也演示一个偏函数。 +1 用于使用函数 def。虽然我会完全避免使用 lambda。
【解决方案2】:

这里要理解的重要一点是,函数是在评估列表推导期间创建的,但 i 的值只会在函数执行期间评估。

因此,在您的情况下,您创建了三个函数,它们都引用了i。在运行时调用这些函数时,i 将具有值 2,因为这是在 for 循环的最后一次迭代中绑定到 i 的最后一个值。


相反,您需要在创建的每个函数中保留i 的当前值。所以,通常的做法是包含一个默认参数,像这样

>>> lambdas = [lambda i=i: i for i in range(3)]
>>> lambdas[0]()
0
>>> lambdas[1]()
1
>>> lambdas[2]()
2

现在,lambda i=i: i 创建一个默认参数 i,它将具有循环变量 i 的当前值。所以,当函数执行时,所引用的i实际上是传递给函数的参数,而不是循环变量。

为避免混淆,您可以选择为变量使用不同的名称,如下所示

>>> lambdas = [lambda t=i: t for i in range(3)]

如果您正在寻找其他方法来避免这种情况,您可以使用 map 函数将数字应用到另一个函数,该函数将生成具有当前值的新函数,像这样

>>> lambdas = map(lambda x: lambda: x, range(3))
>>> lambdas[0]()
0
>>> lambdas[1]()
1
>>> lambdas[2]()
2

这里,lambda x: lambda: x 创建了一个匿名函数,它接受单个参数 x,并返回另一个不接受任何参数但返回值 x 的函数。

注意:不要在实际代码中使用这种map 形式。它可能会降低代码的可读性。

【讨论】:

  • @Kasra 没错,这会减少混乱。感谢您的建议,我已将其包含在答案中。
  • @thefourtheye 您的回答有点令人困惑,为什么在一种情况下使用map 而在另一种情况下使用[... for i in ...]?为什么不map(lambda v: lambda x=v: x, range(3))[(lambda v: lambda: v) (i) for i in range(3)]?我阅读您的答案的方式是第二种解决方案是使用map,而实际上它是使用lambda v: lambda: v
  • @thefourtheye 我理解你的答案,我只是说你的答案是这样写的,看起来你说第二个解决方案的重要部分是map 而实际上它是lambda x: lambda: x。在这两种情况下我都会使用[... for i in ...]map 以避免可能的混淆。
  • @Holt 我明白你现在在说什么了。我现在稍微更新了文本。请检查。
【解决方案3】:

您可以使用functools.partial通过部分应用从更通用的函数创建专用函数,从而将参数计数减少一个:

from functools import partial
lambdas = [partial(lambda x: x, i) for i in range(3)]

这里,lambda x: x 是接受一个参数的通用恒等函数,您可以创建它的三个特化,不接受任何参数,并返回固定值。

【讨论】:

    【解决方案4】:

    啊,进一步谷歌搜索找到了一个解决方案(诚然,我自己不会偶然发现一个)。可以使用默认参数调用所需的行为:

    lambdas = [lambda i=i: i for i in range(3)]

    【讨论】:

      猜你喜欢
      • 2018-09-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-09
      • 2017-08-05
      • 2020-07-13
      • 2020-01-15
      • 1970-01-01
      相关资源
      最近更新 更多