>>> def F(name):
def f():
print name
return f
>>> f1=F('f1')
>>> f2=F('f2')
>>> f1()
f1
>>> f2()
f2
F 是一个返回函数的函数。对于 F 产生的函数 f1、f2,其依赖于F的本地变量 name,但是在F执行结束后,函数 f1、f2 依然可以访问 name,而且相互独立。就像在返回内层函数时,把产生它的环境一块儿打包返回了,这种语法现象叫闭包(closure)。
在《Programming in Lua 2nd》书籍中展示了闭包的各种绚丽的用法,感觉 lua 把闭包和元编程用到极致了。即使不打算用 lua,也强烈推荐读一读,对了解 python 和其他动态语言的本质和实现或有帮助。
在 lua 中有这样一种写法:
>> function gen_ins(init)
.. local i=init-1
.. return function() i=i+1; return i end
.. end
>> ins5=gen_ins(5)
>> ins2=gen_ins(2)
>> ins5()
=> 5
>> ins2()
=> 2
但是这在 python 中却行不通:
>>> def gen_ins(init):
i=init-1
def ins():
i=i+1
return i
return ins
>>> ins0=gen_ins(0)
>>> ins0()
Traceback (most recent call last):
File "<pyshell#111>", line 1, in <module>
ins0()
File "<pyshell#108>", line 4, in ins
i=i+1
UnboundLocalError: local variable 'i' referenced before assignment
这就是所谓的 python 不支持真闭包,python 中对外层变量不能赋值。一般在 python 的函数中,变量的查找顺序是局部符号表,全局符号表,内置符号表。我们知道,全局变量可以被引用,但是要赋值的话,必须用 global 声明一下,否则其实是新建了一个本地变量。从python解释器的角度看,“i=i+1”中 i 既然被赋值,就应该当做本地变量,所以就会发生上述 UnboundLocalError 错误。i 也并不是全局变量,所以在 ins 中用 global 声明了 i 则会找不到全局变量。按照 PEP3104 来看,这是一个由来已久的问题。
PEP3104 中提到一种解决办法如下:
>>> def gen_ins(init):
class T: pass
t=T()
t.i=init-1
def ins():
t.i=t.i+1
return t.i
return ins
>>> ins0=gen_ins(0)
>>> ins0()
0
称为"wrapping it in a mutable object",看起来很诡异。因为Guido觉得不能改动关键字 global 的含义,所以在 python 3 中引入了 nonlocal 关键字,总算解决了这个问题。
前面的闭包算是装饰器的引子了。装饰器有两种形式:
@A
def foo():
pass
相当于:
def foo():
pass
foo = A(foo)
而:
@A(arg)
def foo():
pass
则相当于:
def foo():
pass
foo = A(arg)(foo)
可以看出第一种的装饰器是个返回函数的函数,第二种的装饰器是个返回 返回函数的函数 的函数。两种装饰器的简单示例:
def A(func):
def newfunc(*args, **argkw):
print 'A'
return func(*args, **argkw)
return newfunc
def A(arg):
def _A(func):
def newfunc(*args, **argkw):
print arg
return func(*args, **argkw)
return newfunc
return _A
装饰器中的嵌套定义的函数就涉及到 python 中闭包的问题。
装饰器可以做很多事,比如在原函数调用前检查参数,或者检查登陆状态,调用后记录日志什么的。python 中默认有 staticmethod 和 classmethod 两个装饰器。
staticmethod用来声明类的静态方法,这样调用时就不会传入实例对象(self):
>>> class T:
name = 'T'
@staticmethod
def getname():
print T.name
>>> T.getname()
T
classmethod 修饰那些直接以类对象作为参数的方法:
>>> class T:
name = 'T'
@classmethod
def getname(a_class): print a_class.name
>>> T.getname()
T
其实更”正常“的是不用装饰器:
>>> def getname(a_class): print a_class.name
>>> class T:
name = 'T'
getname = classmethod(getname)
>>> T.getname()
T