这篇文章上周起了个头,把代码帖上来了,没有写完。在今年最后一天,把它写完。
装饰器是闭包的一种扩展。装饰器不仅仅用于函数和类的成员函数,还用于对类的装饰。不过,殊路同归,都是名字的重绑定。只是有的名字是函数,有的是类罢了。
装饰器让我想到了基于方面的编程。基于对象的编程在纵向的模块解耦设计方面有天生的优势,但是在横向的模块通用设计(如日志)方面的优势并不明显。闭包和装饰器在这方面却有得天独厚的优势。
什么是闭包呢?
闭包相当于函数对象。函数对象不仅包含一段代码,更重要的是包含了状态,也就跟类对象平起平坐了。换句话说,函数对象不再像函数一样是全局唯一的了。你在A处调用的函数F,跟在B处调用的函数F,是同一个函数。但是,包含了状态的函数对象不再是这样的了。在A处调用的函数对象FO,跟在B处调用的函数对象FO可能就不再是同一个对象了。为啥?因为A处的FO跟B处的FO可能有不同的状态。想想类和对象的区分。
闭包有什么用呢?一个很重要的作用是闭包将函数调用跟参数绑定解耦了。如下函数func,在调用的时候,必须要把参数一起传入。
func(a, b, c)
而闭包怎么处理呢?如下,第一句是将func的参数绑定,第二句是不带参数的调用func。
func_closure = func(a, b, c)
func_closure()
python的闭包怎么实现呢?从上面第一句可以看出,func_closure也是一个函数,也就是说,func返回值是一个函数。
返回函数的函数就是闭包呢?也不尽然,当func_closure引用的函数访问func的参数或者func定义的变量时,func_closure才是闭包。
说了这么多,下面以一个日志的例子来说明。需求是记录函数的参数,返回值和执行时间。
代码如下:
import time
def log_decorator(show_para):
def log_decorator_impl(func):
def wrapper(*args, **kwargs):
if func.__doc__ is not None:
print func.__doc__
starttime = time.time()
retcode = func(*args, **kwargs)
endtime = time.time()
print func.__name__, '(',
if show_para:
if args != ():
print '#', args, '#',
if kwargs != {}:
print '##', kwargs, '##',
print') return', retcode, 'costs', endtime - starttime, 's.'
return retcode
return wrapper
return log_decorator_impl
@log_decorator(False)
def hello():
'''say hello to everybody.'''
print 'hello'
@log_decorator(True)
def calc(x=0, y=0):
'''add two number.'''
return x + y
@log_decorator(True)
def calc_numbers(numbers):
'''add numbers in list'''
total = 0
for anumber in numbers:
total += anumber
return total
if __name__ == '__main__':
hello()
calc(3, 9)
calc(y=11)
numbers = [1, 3, 5, 9]
calc_numbers(numbers)
输出 如下:
say hello to everybody.
hello
hello ( ) return None costs 0.0330429077148 s.
add two number.
calc ( # (3, 9) # ) return 12 costs 4.05311584473e-06 s.
add two number.
calc ( ## {'y': 11} ## ) return 11 costs 4.05311584473e-06 s.
add numbers in list
calc_numbers ( # ([1, 3, 5, 9],) # ) return 18 costs 4.05311584473e-06 s.
log_decorator函数内部定义了两个嵌套函数,它是一个闭包吗?让python解释器来告诉我们:
>>> dir(log_decorator)
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
>>> log_decorator.__closure__
>>> print log_decorator.__closure__
None
>>> dir(log_decorator_impl)
Traceback (most recent call last):
File "<pyshell#4>", line 1, in <module>
dir(log_decorator_impl)
NameError: name 'log_decorator_impl' is not defined
log_decorator有一个属性__closure__,这个属性保存环境变量,也就是log_decorator的状态。从执行结果看,log_decorator的__closure__是None,也就是说,它没有环境变量。所以它不是闭包。
>>> a=log_decorator(True)
>>> dir(a)
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
>>> a.__closure__
(<cell at 0x1048b0c20: bool object at 0x100172390>,)
>>> dir(a.__closure__)
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index']
a就是log_decorator_impl,它的__closure__不是空的,里面保存了一个bool对象!很可能就是调用log_decorator的参数。如果这个bool对象是True,那就肯定是了。
>>> type(a.__closure__)
<type 'tuple'>
>>> len(a.__closure__)
1
>>>a.__closure__[0]
<cell at 0x1048b0c20: bool object at 0x100172390>
>>> type(a.__closure__[0])
<type 'cell'>
>>> dir(a.__closure__[0])
['__class__', '__cmp__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'cell_contents']
>>> a.__closure__[0].cell_contents
True
a.__closure__是一个有一个元素的元组(现实跟猜测总有一段距离),元组的元素类型是cell。cell只有一个正常的属性cell_contents(前后没有下划线),从名字上看应该是保存的值。再一查,果然是True!
>>> def test4closure():
pass
>>> test4closure
<function test4closure at 0x1048b1848>
>>> b=a(test4closure)
>>> dir(b)
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
>>> type(b)
<type 'function'>
>>> type(b.__closure__)
<type 'tuple'>
>>> dir(b.__closure__)
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index']
>>> len(b.__closure__)
2
>>>dir(b.__closure__[0])
['__class__', '__cmp__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '<span style="color:#ff0000;">cell_contents</span>']
>>> b.__closure__[0]
<cell at 0x1048b0be8: function object at 0x1048b1848>
>>> b.__closure__[0].cell_contents
<function test4closure at 0x1048b1848>
>>> b.__closure__[1].cell_contents
True
wrapper也是个闭包,保存了log_decorator的参数show_para和log_decorator_impl的参数func。保存的顺序估计是由内到外。
再调用b看看:
>>> b()
test4closure ( ) return None costs 3.81469726562e-06 s.
>>> b(1)
Traceback (most recent call last):
File "<pyshell#44>", line 1, in <module>
b(1)
File "/Users/yangjia/decorator_test.py", line 9, in wrapper
retcode = func(*args, **kwargs)
TypeError: test4closure() takes no arguments (1 given)
b调用test4closure,并统计了它的参数、返回值和执行时间。test4closure没有参数,所以传给b参数会返回失败。
@log_decorator(False)
def hello():
在函数定义前一行@一下返回函数的函数。其实装饰器是个语法糖。没有它也能用,有它更方便。
def hello():
'''say hello to everybody.'''
print 'hello'
hello = log_decorator(False)(hello)
# log_decorator(False)->log_decorator_impl
# log_decorator_impl(hello)->wrapper
这有两次函数调用,第一次调用log_decorator(False)返回log_decorator_impl,第二次调用log_decorator_impl(hello)返回wrapper。
>>> type(hello)
<type 'function'>
>>> dir(hello)
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
>>> hello.__closure__
(<cell at 0x104878d38:function object at 0x1004f0cf8>, <cell at 0x104878d00: bool object at 0x100172370>)
>>> dir(hello.__closure__)
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index']
>>> type(hello.__closure__)
<type 'tuple'>
>>> len(hello.__closure__)
2
>>> hello.__closure__[0]
<cell at 0x104878d38: function object at 0x1004f0cf8>
>>> type(hello.__closure__[0])
<type 'cell'>
>>> dir(hello.__closure__[0])
['__class__', '__cmp__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'cell_contents']
>>> hello.__closure__[0].cell_contents
<function hello at 0x1004f0cf8>
>>> hello.__closure__[1].cell_contents
False
hello是一个闭包,有两个元素,一个是hello函数本身,另一个是og_decorator的参数show_para(值是False)。
>>> hello
<function wrapper at 0x1004f0d70>
>>> calc
<function wrapper at 0x1050b79b0>
>>> calc_numbers
<function wrapper at 0x1050b7b18>
>>> print '%#x' %id(hello)
0x1004f0d70
>>> print '%#x' %id(calc)
0x1050b79b0
>>> print '%#x' %id(calc_numbers)
0x1050b7b18
>>> hello == calc
False
>>> hello == calc_numbers
False
>>> calc == calc_numbers
False
显然,hello不再是hello,而是个wrapper。calc和calc_numbers也是wrapper。hello、calc和calc_numbers是三个不同的wrapper对象。