写在前面
装饰器需要一点函数的知识:在函数中定义函数;从函数中返回函数;将函数作为参数传给另外一个函数。我假设读者有这样的先验知识。
1. 什么是装饰器
装饰器就是用来包装函数的,在不能改变原有函数的功能属性的基础上,可以再增加一些功能。
例如我装修一个房子,如果不隔音,我在墙上加一层隔音板,却不能把墙拆了,换成隔音材质的。
程序中也是一样的,不会对原有函数造成改变,还要添加新的功能,调用函数时的接口没有变化。
2. 用装饰器给函数增加计时的功能
2.1 方案一
- 就是最原始的python代码
import time
def index():
time.sleep(2)
start_time = time.time()
index()
end_time = time.time()
print(end_time - start_time)
- 思考一个问题,如果我们的函数有很多个,每一个函数都需要计时,这时候,方案一的代码写下来会变得非常的冗余,所以我们需要一点改进。
2.2 方案二——函数作为参数
- 这里我们可以实现任何函数的计时功能,但是有一个问题,我们说到装饰器不改变原有函数的调用接口(方案二中并没有调用index函数实现,而是调用了calculate_time)。(举个例子:假如玩游戏时我们习惯用“WASD”操作方向,突然变成了“ZXCS”,那么我们肯定就不太习惯要调回来)。我们需要再方案三中解决这个问题。
def calculate_time(f):
start_time = time.time()
f()
end_time = time.time()
print(end_time - start_time)
calculate_time(index)
2.3 方案三——使用闭包
- 我们使用闭包,这样就可以不改变原函数的接口了
def calculate_time(f):
def inner():
start_time = time.time()
f()
end_time = time.time()
print(end_time - start_time)
return inner
index = calculate_time(index)
index()
2.4 方案四——装饰器
-
装饰器的作用就相当于下面这行代码,语法为@装饰器
index = calculate_time(index) -
值得注意的是,我们需要先定义装饰器,再定义需要被装饰的函数,不能反。
def calculate_time(f):
def inner():
start_time = time.time()
f()
end_time = time.time()
print(end_time - start_time)
return inner
@calculate_time # 等价于index = calculate_time(index),翻译为用@后面的函数去装饰下面的函数
def index():
time.sleep(2)
index()
- 该方案存在一点小瑕疵:当我们使用魔方函数__name__时,发现index.__name__返回的结果为inner,即
print(index.__name__)
# result
inner
- 这并不是我们想要的,Ouput输出应该是"index"。这里的函数被inner替代了。它重写了我们函数的名字和注释文档(docstring)。幸运的是Python提供给我们一个简单的函数来解决这个问题,那就是functools.wraps。我们修改上一个例子来使functools.wraps:
2.5 方案五——考虑魔方函数__name__
- 注意:@wraps接受一个函数来进行装饰,并加入了复制函数名称、注释文档、参数列表等等的功能。这可以让我们在装饰器里面访问在装饰之前的函数的属性。
- 在此方案中,我们又加入了一个新功能,返回ret值,给index函数加了一个返回值
from functools import wraps
def calculate_time(f):
@wraps(f)
def inner():
start_time = time.time()
ret = f()
end_time = time.time()
print(end_time - start_time)
return ret
return inner
@calculate_time
def index():
time.sleep(2)
return "index"
index() # 输出时间和字符串"index"
print(index.__name__) # index
2.6 最终版——适配多参数
- 很多情况下,我们需要让装饰器去装饰多个参数的函数,这时候需要用到多值参数
- 这里我们定义了需要被修饰的add函数,返回两个数之和。
from functools import wraps
def calculate_time(f):
@wraps(f)
def inner(*args,**kwargs):
start_time = time.time()
ret = f(*args,**kwargs)
end_time = time.time()
print(end_time - start_time)
return ret
return inner
@calculate_time
def add(m,n):
time.sleep(2)
return m + n
add(1,2)