软件开发中最重要的一条真理就是“不要重复自己的工作”,如果有一段代码与其他代码高度重合,这个时候需要一个优雅的解决方案、在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的结果,因此没有任何额外打印返回