首先声明,我用的是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的即时值,并保存起来。