软件开发中最重要的一条真理就是“不要重复自己的工作”,如果有一段代码与其他代码高度重合,这个时候需要一个优雅的解决方案、在python中,这类问题称作元编程。

python中基于这个目的的主要特性包含装饰器,类装饰器以及元类。今天要讲的就是Python的装饰器。

 

装饰器的通常是在包装层做一些额外的处理。比如记录日志,统计运行时间等等,来见第一个装饰器,它的主要作用是计时:

 1 import time
 2 
 3 from functools import wraps
 4 
 5 def timethis(func)
 6 
 7     @wraps(func)
 8     def wrapper(*args, **kwargs):
 9         start_time = time.time()
10         rv = func(*args, **kwargs)
11         end_time = time.time()
12         print func.__name__, (end_time - start_time)
13         return rv
14     return wrapper

然后使用这个装饰器:

1 import time
2 
3 @timethis
4 def time_sleep_count(sleep_time):
5 
6     time.sleep(sleep_time)

装饰器的执行效果和下面的效果是一样的:

1 def time_sleep_count(sleep_time)
2     pass
3 
4 time_sleep_count = timethis(time_sleep_count)

在后续的介绍中,会提及一些内建的解释器,比如@staticmethod, @classmethod, @property,它们的工作方式和上述的结果是一样的。

 

总结一下,装饰器内部的代码一般会涉及创建一个新的函数,利用*args 和**kwargs来接受任意的参数。示例中的装饰器正是如此,在wrapper函数内部调用原来的函数及其参数,并返回原来的结果,另外,也可以返回一些自定义的结果,wrapper里面返回的是原来函数的结果,并顺带打印了一些描述语句,比如运行耗时等。它的返回结果取代了原来的func。

 

一般来说装饰器不会修改调用签名,也不会修改被包装的函数返回结果。这里还有一些细节需要描述,比如wrapper函数外面还有一个装饰器,@wraps,它可以用来保存函数的元数据,我在下面会继续描述,这个装饰器的具体作用。

我们可以用dir(object)来观察一下一个对象有哪些元数据,常见的 __name__,__doc__等,我们可以用__doc__属性来测试一下wraps在其中的作用。

首先把上面的代码稍微丰富一下:

 1 import time
 2 
 3 from functools import wraps
 4 
 5 def timethis(func):
 6     '''
 7     Get the func time cost.
 8     '''
 9     @wraps
10     def wrapper(*args, **kwds)
11         b_time = time.time()
12         rv = func(*args, **kwds)
13         e_time = time.time()
14         print (func.__name__, e_time - b_time)
15         return rv
16     return wrapper
17 
18 @timethis
19 def count_num(input_n):
20         
21     '''
22     Count down the num
23     '''
24     while n > 0:
25         n -= 1

关注一下两个__doc__和__name__,如果在不使用wraps的情况下,使用count_num.__doc__以及count_num.__name__和使用了wraps的完全不同,那么wraps里面保存的是谁的元数据呢?它保存的是被装饰的函数的元数据:

 1 ####未使用wraps
 2 
 3 print count_num.__name__
 4 
 5 ###wrapper
 6 
 7 print count_num.__doc__
 8 
 9 ###由于wrapper函数没有文档描述,故此处打印空
10 
11 ####使用wraps
12 
13 print count_num.__name__
14 
15 ###count_num
16 
17 print count_num.__doc__
18 
19 ###Count down the num

@wraps装饰器的一个重要特性是可以通过__wrapped__属性来访问被包装的那个函数,比如使用count_num.__wrapped__(10000),它调用的结果就是未被装饰过的count_num的结果,因此没有任何额外打印返回