如何使用列表理解生成不同 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
是接受一个参数的通用恒等函数,您创建它的三个特化,不接受参数,并返回固定值。
这个问题是从涉及 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
是接受一个参数的通用恒等函数,您创建它的三个特化,不接受参数,并返回固定值。