装饰器就是一个函数,用来给别的函数加能力,同时尽量不改它的源码、也不改调用方式。对应开放封闭:对扩展开放,对修改封闭。
下面用「给函数计时」把写法一步步推出来。
先把失败方案走一遍
直接改源码:调用方式没变,但函数本身被改了。
在调用点外包一层计时:源码没动,但每个调用点都要写一遍,也容易漏。
抽成 wrapper():冗余没了,但调用方要从 index() 改成 wrapper()。
写成真正的装饰器
把被装饰对象当参数传进去,用 *args, **kwargs 原样转发,并把返回值传回去:
python
import time
from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(time.time() - start)
return result
return wrapper
@timer
def index(x, y, z):
time.sleep(0.1)
print("index", x, y, z)
index(1, 2, 3)@timer 就是 index = timer(index)。语法糖只是少写这一句赋值。
@wraps 为什么要写
不加 @wraps(func) 时,index.__name__ 会变成 wrapper,文档字符串也会被换掉。pytest、allure 这类靠函数名和签名工作的工具会因此报「少参数」。functools.wraps 把原函数的名字、文档和签名拷回来。
类装饰器同样要加在内层函数上:
python
class MyLog:
def __call__(self, func):
@wraps(func)
def inner(*args, **kwargs):
return func(*args, **kwargs)
return inner多个装饰器
靠近函数的先包上去,离函数远的后包。执行则相反:外层先进入,内层后进入。
python
@decorator1
@decorator2
def func():
...
# 等价于 func = decorator1(decorator2(func))口诀:先装饰后执行,后装饰先执行。