前言
软件开发中最重要的一条真理是"don’t repeat yourself",即不要重复自己的工作。也就是说,在任何时候,需要创建高度重复的代码或者需要复制粘贴源码时,通常这时都需要寻找一个更加优雅的解决方案!!!在PYTHON中,我们称这为"元编程"!
元编程的主要目的是创建函数和类,并用它们来操纵代码;例如:修改、生成或包装已存在的代码。python中基于这个目的的主要特性包括装饰器,类装饰器以及元类。
问题
给函数加上一个包装层(wrapper layer)来添加额外的处理,例如记录日志,记录运行时间等。
解决方案
# coding:utf-8
import time
from functools import wraps
def timethis(func):
"""
Decorator that reports the execution time.
:param func:
:return:
"""
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(func.__name__, end-start)
return result
return wrapper
@timethis
def countdown(n):
while n > 0:
n -= 1
if __name__ == '__main__':
# countdown(10000)
print(countdown.__name__) # 函数名
print(countdown.__doc__) # 文档字符串
print(countdown.__annotations__) # 注解
结果
C:\Users\tianming\AppData\Local\Programs\Python\Python37-32\python.exe H:/test/wrapAfunc.py
countdown 0.0009984970092773438
countdown
counts down
:param n:
:return:
{'n': <class 'int'>}
Process finished with exit code 0
去掉@wraps(func)结果
C:\Users\tianming\AppData\Local\Programs\Python\Python37-32\python.exe H:/test/wrapAfunc.py
countdown 0.0009801387786865234
wrapper
None
{}
Process finished with exit code 0
discuss
1.在装饰器内部一般会涉及创建一个新的函数,利用*args和**kwargs来接受任意的参数。(wrapper正是这么做的)
2.在wrapper函数内部需要调用被包装的函数(它是装饰器的输入参数)并返回它的结果。
3.在这个包装函数中也可以添加任何想要添加的代码,例如计时处理等。
4.新创建的wrapper函数会作为装饰器的结果返回,取代了原来的函数。
5.装饰器一般来说不会修改函数的签名,也不会修改被包装函数返回的结果
当把写好的装饰器用在一个函数上时,为了不丢失被装饰函数的元数据(如:函数名,文档字符串,函数注解,调用签名),应该总是在底层被包装函数上添加functools库中的@wraps装饰器