stackeoverflow上对python闭包的讨论

最近看到stackoverflow上对python闭包的讨论,很有意思,在此记录一下。原文可以去http://stackoverflow.com/questions/233673/lexical-closures-in-python

问题起源是有个程序员提了这么个问题;
flist =[]
for i in xrange(3):
  def func(x):
    return x * i
  flist.append(func)

for f in flist:
  print f(2)
本来预期的结果是生成三个函数,每个函数闭合了变量i,i分别为0 1 2,所以最后打印的结果应该是0 2 4

我试了一下这段代码,打印出来是 4 4 4。
按照这个结果,三个函数中闭合的i都是2了。

这是怎么回事呢?
首先,代码确实是创建了三个函数,并且也放入了flst,但是问题就出在这三个函数的闭包闭合是同一个变量i,所以最后在调用这三个函数的时候,三个闭包都指向了i,而i最后一次修改后的值就是2,所以结果就是4 4 4了。

stackoverflow有一段回答非常好,我这里就直接引用了:
What is happening is that the variable i is captured, and the functions are returning the value it is bound to at the time it is called. In functional languages this kind of situation never arises, as i wouldn't be rebound.
大意就是:变量i被捕捉(闭包)了。。。在函数式语言中,这种情况不会发生,因为i永远不会被改变。

在python中,for对i产生了副作用,改变了i的值。如果是函数式语言,变量是不会变化的,所以我猜测在函数式语言中,如果要做循环,会创造出3个变量分别等于0 1 2,这样就保证了三个函数分别闭合了三个不同变量,就能正确工作了。

为了解决这个问题,大家给了不少方法.
第一个方法再嵌套了一个函数,这样闭包闭合的就是嵌套函数的形参j,因为每次j都不一样,就保证了不会闭合成同一个变量。
flist =[]
for i in xrange(3):
  def funcC(j):                  #每个函数绑定的是j,就不会出现统统绑定到i的问题了。
    def func(x):
      return x * j
    return func
  flist.append(funcC(i))
第二个方法这个更短,不过i=i,我感觉不太好理解,形参和默认值弄成一样了。
 
  
flist =[]
for i in xrange(3):
  def func(x, i=i):# the *value* of i is copied in func() environment 
    return x * i
  flist.append(func)
最后这个方法,我感觉最好理解,通过map函数确保三个函数的闭包闭合的是不同的变量:
flist =[]
def loop_body(i):# extract body of the for loop to function
  def func(x):
    return x*i
  flist.append(func)

map(loop_body, xrange(3))# for i in xrange(3): body
当然,还可以把这个写得更精简:
>>> flst = map(lambda x: lambda i: x *i, xrange(3))
>>> map(lambda fn: fn(1), flst)
[0, 1, 2]

stackoverflow上也有坑爹答案,还有人回答说i是全局变量。。。
还好通过投票,有用的答案被顶上去了,要是我这种懒人第一眼看到“i是全局变量”的答案,没准就信了。

PS:这个提问者还提了一个同样的问题,他用lisp的dotimes和scheme的do分别实现了闭包,结果发现lisp的表现跟python一样,而scheme得到的三个函数确闭合了三个不一样的值。提问的人还以为lisp的不同语言对词法范围设计不同,其实是lisp的dotimes跟python一样,利用副作用改变了变量值,用了同一个变量做循环,导致了相同的问题。我用lisp的loop宏也试了一下,结果也是一样,看来dotimes和loop用的都是同一个变量做循环。

CL-USER> (setf flst (loop for i in '(1 2 3) collect (lambda (x) (* x i))))
(#<CLOSURE (LAMBDA #) {B66263D}> #<CLOSURE (LAMBDA #) {B662655}>
#<CLOSURE (LAMBDA #) {B66266D}>)
CL-USER> (mapcar (lambda (fn) (funcall fn 1)) flst)
(3 3 3)

如果用map,就可以避免这个问题:
CL-USER> (mapcar (lambda (fn) (funcall fn 1)) (mapcar (lambda (x) (lambda (i) (* x i))) '(1 2 3)))
(1 2 3)
这样结果就对了,确实是闭合了三个不同的变量。
实际上lisp的dotimes是用do实现的宏,loop更是lisp提供的一个超级循环宏,实现的时候用了副作用,这真是要小心的一个地方,感觉宏也很危险啊!

转载于:https://www.cnblogs.com/wsecurity/p/3277270.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值