Python Lambda绑定到本地值


76

下面的代码吐出了1两次,但是我希望看到0,然后1

def pv(v) :
  print v

x = []
for v in range(2):
  x.append(lambda : pv(v))

for xx in x:
  xx()

我期望python lambdas可以在后台绑定到本地变量指向的引用。但是,事实并非如此。我在大型系统中遇到了这个问题,其中lambda正在做现代C ++的bind等效操作(例如'boost :: bind'),在这种情况下,您将绑定到智能ptr或copy构造lambda的副本。

那么,如何将局部变量绑定到lambda函数并在使用时保留正确的引用?我对这种行为很不满意,因为我不希望带有垃圾收集器的语言有这种行为。


2
我已经看到了这个问题的几种变体。有人需要汇总所有内容,将标题更改为令人难忘的内容,然后告诉互联网给Google
Shep 2012年

2
啊,你去了,几乎重复了:python-closures-in-python
Shep 2012年

@shep,是的,我无法正确说出它,我正在尝试复制lua的工作方式。
哈桑·赛义德

2
Python的一个奇怪之处是循环变量在循环之后仍然存在,因此在循环内部定义并引用循环变量的函数不会NameError在循环外部抛出if。在大多数其他语言中,循环变量只能在循环本身内访问。
BallpointBen

Answers:


117

更改x.append(lambda : pv(v))x.append(lambda v=v: pv(v))

您期望“ python lambdas在后台绑定到本地变量指向的引用”,但这不是Python的工作方式。Python在调用函数时(而不是在创建函数时)查找变量名。使用默认参数是可行的,因为默认参数是在创建函数时(而不是在调用函数时)评估的。

这对于lambdas而言并不特殊。考虑:

x = "before foo defined"
def foo():
    print x
x = "after foo was defined"
foo()

版画

after foo was defined

有趣的是,请您详细说明一下力学原理。
哈桑·赛义德

5
一个更明显的替代方法是使用functools.partial或使用单独的帮助器函数创建闭包(def make_closure(v): return lambda: pv(v)您可以将其放入test)。

所以我的真实代码应该是lambda par1, par2 = l3_e : self.parse_l4(par1, par2)什么?
哈桑·赛义德

如果v本身就是引用(而不是int),那么我认为不会有“深层”副本吗?
亚伦·麦克戴德2014年

@AaronMcDaid不会。
EkremDinçel20年

29

lambda的闭包保留对正在使用的变量的引用,而不是其值的引用,因此,如果变量的值稍后更改,则闭包中的值也会更改。也就是说,闭包变量的值是在调用函数时(而不是在创建函数时)解析的。(就其价值而言,Python的行为在函数式编程世界中并不罕见)。

有两种解决方案:

  1. 使用默认参数,在定义时将变量的当前值绑定到本地名称。 lambda v=v: pv(v)

  2. 使用双lambda并立即调用第一个。 (lambda v: lambda: pv(v))(v)


我喜欢第二种方法,因为我想冻结在其当前值的变量不一定是lambda的参数。
ga

@ga值不必已经是lambda的参数,第一种技术才能起作用。相反,真的
kindall
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.