Python装饰器用一个可调用对象包装另一个函数,从而在不改原函数源码的前提下增加行为,例如打印日志、统计耗时、做权限判断。@装饰器 是语法糖,等价于手工赋值 函数 = 装饰器(函数)。实现上,装饰器通常返回一个内层函数,也就是闭包。

先套一层

def my_decorator(func):
    def wrapper():
        print("函数执行前")
        func()
        print("函数执行后")
    return wrapper

@my_decorator
def say_hi():
    print("Hi!")

say_hi()

输出:

函数执行前
Hi!
函数执行后

@my_decorator 写在定义上方时,解释器会执行 say_hi = my_decorator(say_hi),之后名字 say_hi 指向的是包装后的函数。

本质:接收函数,返回函数

say_hi = my_decorator(say_hi)(@ 做的事) wrapper:包装后返回的新函数 func 原函数
装饰器给函数穿外套:wrapper 在外层加日志/计时等能力,再调用 func,@ 只是 say_hi = my_decorator(say_hi) 的语法糖。
def my_decorator(func):
    def wrapper():
        func()
    return wrapper

@my_decorator
def f1():
    pass

def f2():
    pass
f2 = my_decorator(f2)

@ 与不带 @ 的两种写法,效果相同。

原函数带参数

包装函数需要把位置参数和关键字参数原样转交,常用 *args / **kwargs,并 return 原函数的返回值:

def log(func):
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__}, 参数={args}")
        return func(*args, **kwargs)
    return wrapper

@log
def add(a, b):
    return a + b

print(add(3, 5))

计时示例

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        cost = time.time() - start
        print(f"{func.__name__} 耗时 {cost:.4f} 秒")
        return result
    return wrapper

@timer
def slow_work():
    time.sleep(0.1)
    return "done"

print(slow_work())

保留原函数元信息

直接替换后,__name____doc__ 往往会变成 wrapper 的。用 functools.wraps 可以把原函数的元数据拷到包装函数上:

import functools

def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def hello():
    """打个招呼"""
    print("hi")

print(hello.__name__)
print(hello.__doc__)

常见误区

  • 包装函数未声明 *args/**kwargs 时,一旦原函数需要参数就会报错。
  • 调用原函数后忘记 return,调用方拿到的就是 None
  • 不做 wraps 时,调试输出和帮助文档容易对不上原函数名。