现在的位置: 首页 > 综合 > 正文

[动态语言]python的闭包问题

2012年12月21日 ⁄ 综合 ⁄ 共 988字 ⁄ 字号 评论关闭

首先声明,我用的是2.7.1版本的CPython。

 

第一个问题,闭包中的upvalue不可修改:

 1 def foo():
2 i = 0
3 def _foo():
4 i += 1
5 print i
6 return _foo
7
8 f = foo()
9 f()
10 f()
11 f()

错误:local variable 'i' referenced before assignment

可以理解,不用global关键字的话,修改全局变量也会遇到问题。因此这个问题其实是不能修改所有外层变量。python3引入了nonlocal来处理这个问题。如果一定要在python3之前的版本中修改upvalue,可以将upvalue放在list中再修改list项。

python和lua/js的选择相反,不特殊声明的情况下,后两者是访问外层变量,除非显示的用local/var,才是访问局部变量;而python是默认访问局部变量,除非显示用global/nonlocal。python3之前的global关键字太不够用了。

python这样默认是局部变量,可以让你写出简洁的普通函数,而lua那样默认是外层变量,则是鼓励编写匿名函数,因为匿名函数一般都会用作闭包。

 

 

第二个问题,for/try等作用于中的变量居然是全局的!

1 for i in range(5):
2 pass
3 print i
4
5 try:
6 j = 5
7 finally:
8 pass
9 print j

输出:4,5

试想在for之前使用过i这个全局变量,for过后,i被修改了!

一个旨在尽量隐藏语法细节的优秀语言,居然会有这种行为?20年都没有修正,那一定是有它这样做的原因吧...

 

再来看看for当中的i是全局,这种不符直觉的行为的影响:

1 a = [(lambda n: n * i) for i in range(3)]
2
3 for f in a:
4 print f(2)

期望的结果是:0,2,4

实际的结果是:4,4,4

因为i是全局的,所以生成的所有lambda表达式其实代码相同了!

用一个hack来解决:

a = [(lambda n, i=i: n * i) for i in range(3)]

for f in a:
print f(2)

这里为lambda添加了一个默认参数i,因此n * i中的i不再是upvalue,而是参数!又因为,python中的默认参数值是保存在函数对象内的(__defaults__),所以,生成每个匿名函数时,都会读取i的即时值,并保存起来。

抱歉!评论已关闭.