Skip to content

装饰器就是一个函数,用来给别的函数加能力,同时尽量不改它的源码、也不改调用方式。对应开放封闭:对扩展开放,对修改封闭。

下面用「给函数计时」把写法一步步推出来。

先把失败方案走一遍 ​

直接改源码:调用方式没变,但函数本身被改了。

在调用点外包一层计时:源码没动,但每个调用点都要写一遍,也容易漏。

抽成 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))

口诀:先装饰后执行,后装饰先执行。

测试开发工程师 · 专注自动化与系统架构 | 邮箱: hansblog@atumsoul.win