如何使用列表理解生成不同 lambda 函数的列表?

How to generate a list of different lambda functions with list comprehension?

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

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

如果您随后尝试调用生成的 lambda 函数: lambdas[0]()lambdas[1]()lambdas[2]() 全部 return 2.

期望的行为是 lambdas[0]() return 0, lambdas[1]() return 1, lambdas[2])() return 2.

我看到索引变量是按引用解释的。问题是如何改写以按价值对待它。

使用具有默认值的参数将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不是局部变量时,Pythonlooks up its value in the enclosing scope。找到的值是在列表理解的 for 循环中获得的最后一个值 i。这就是为什么在没有默认值参数的情况下,每个 lambda returns 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.

的期望值

作为, 使用已经提供的一些参数值创建新函数的另一种方法 就是使用 functools.partial:

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

这里要理解的重要一点是,函数是在列表推导求值期间创建的,但是 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,而 returns 另一个不接受任何参数但 returns 值的函数x.

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

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

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

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

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

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