在Python中生成函数列表

6
我有以下Python代码,可以生成匿名函数列表:
basis = [ (lambda x: n*x) for n in [0, 1, 2] ]     
print basis[0](1)

我本以为它与之等价
basis = [ (lambda x: 0*x), (lambda x: 1*x), (lambda x: 2*x) ]
print basis[0](1)

然而,第二个代码片段打印出了我期望的0,但是第一个代码片段打印出了2。第一个代码片段有什么问题?为什么它的行为与预期不同?


相关问题:https://dev59.com/UnVC5IYBdhLWcg3w7V_3 - jfs
4个回答

9
你可以使用默认参数来在 n 上创建一个闭包。
>>> basis = [ (lambda x,n=n: n*x) for n in [0, 1, 2] ]     
>>> print basis[0](1)
0

3
这项技术经常用于将方法作为回调函数传递。它看起来类似于 register_event_handler(event=evt, callback=lambda cbVar1, cbVar2, s=self: s.handle_evt(cbVar1, cbVar2)) - Mike DeSimone

4
因为这是“按名称传递”的方式。
也就是说,当运行lambda时,它执行n*xx绑定到1(它是一个参数),n在环境中查找(它现在是2)。因此,结果是2。

这并没有解释为什么当我们使用生成器推导式时,会展现出相同的行为,而这个过程并没有将 n "泄漏" 到 locals() 中。据我所知,n 的值是从 lambda 函数的 .func_closure 中查找的,而当 n 不再存在时,这个过程又是如何神奇地引用到上一次 n 所指向的对象的完全不清楚。 - Karl Knechtel

3
问题在于第一个示例中,每个lambda都绑定到相同的n——换句话说,它捕获了变量本身,而不是变量的值。由于n在循环结束时的值为2,因此每个lambda都使用值2作为n的值。
显然,您可以使用默认参数来解决这个问题:
basis = [ (lambda x,n=n: n*x) for n in [0, 1, 2] ]
print basis[0](1) 

由于默认参数值是常量,n=n右侧的n将在每次循环中被求值,以给您一个新的捕获值。

有关如何修复它的任何建议? - D R
1
Celil:我编辑了我的答案,提供了一个建议的修复方法,但并不是非常优雅。 - Gabe
默认参数可能不太优雅,但它们比我以前使用的显式部分求值要容易得多:basis = [(lambda n:(lambda x: n * x))(n) for n in range(3)]。:/ 默认参数在不可变的意义上并不是“常量”,但它确实会被评估并绑定到 lambda 的 .func_defaults 中。闭包参数也是如此,但显然以一种更神奇的方式... - Karl Knechtel
Karl:所谓“常量”,是指表达式只被计算一次且永不改变。当然,如果该表达式计算结果为引用,则可以更改其所引用的对象,但无法更改引用本身。 - Gabe

0

我想帮助理解Karl Knechtel(2010年12月13日7:32)的评论。以下代码展示了如何使用生成器,原始的lambda定义可以得到预期的结果,但是使用列表或元组则不行:

>>> #GENERATOR
... basis = ( (lambda x: n*x) for n in [0, 1, 2] )  
>>> print(type(basis))
<type 'generator'>
>>> basis = ( (lambda x: n*x) for n in [0, 1, 2] ) 
>>> print([x(3) for x in basis])
[0, 3, 6]
>>> #TUPLE
... basis = tuple( (lambda x: n*x) for n in [0, 1, 2] )
>>> print(type(basis))
<type 'tuple'>
>>> print([x(3) for x in basis])
[6, 6, 6]
>>> #LIST
... basis = list( (lambda x: n*x) for n in [0, 1, 2] )
>>> print(type(basis))
<type 'list'>
>>> print([x(3) for x in basis])
[6, 6, 6]
>>> #CORRECTED LIST
... basis = list( (lambda x, n=n: n*x) for n in [0, 1, 2] )
>>> print(type(basis))
<type 'list'>
>>> print([x(3) for x in basis])
[0, 3, 6]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接