Python 装饰器详解

装饰器(Decorator)是 Python 中一种在不修改原函数代码的前提下,给函数增加额外功能的语法特性。本质上就是一个接收函数、返回函数的函数。

1. 核心思想

函数在 Python 中是一等公民——可以赋值给变量、作为参数传递、作为返回值。装饰器就建立在这个基础上:

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("函数执行前")
        result = func(*args, **kwargs)
        print("函数执行后")
        return result
    return wrapper

@my_decorator
def say_hello(name):
    print(f"Hello, {name}")

say_hello("Alice")
# 等价于: say_hello = my_decorator(say_hello)

输出:

函数执行前
Hello, Alice
函数执行后

@ 语法只是语法糖,@my_decorator 就是执行 say_hello = my_decorator(say_hello)

2. 常见应用场景

计时器

import time
from functools import wraps

def timer(func):
    @wraps(func)  # 保留原函数的 __name__、__doc__ 等元信息
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        print(f"{func.__name__} 耗时 {time.perf_counter() - start:.4f}s")
        return result
    return wrapper

日志 / 权限校验 / 缓存

def require_login(func):
    @wraps(func)
    def wrapper(user, *args, **kwargs):
        if not user.get("logged_in"):
            raise PermissionError("请先登录")
        return func(user, *args, **kwargs)
    return wrapper

3. 带参数的装饰器(三层嵌套)

装饰器本身接受参数时,需要再多包一层:

def repeat(times):              # 1. 接收装饰器参数
    def decorator(func):        # 2. 接收函数
        @wraps(func)
        def wrapper(*args, **kwargs):  # 3. 真正执行
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def greet():
    print("hi")

4. 类装饰器

通过实现 __call__ 让类的实例可以像函数一样被调用:

class CountCalls:
    def __init__(self, func):
        self.func = func
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"第 {self.count} 次调用")
        return self.func(*args, **kwargs)

@CountCalls
def ping():
    return "pong"

适合需要维护状态的场景(调用计数、缓存等)。

5. 内置装饰器速览

装饰器 作用
@staticmethod 静态方法,不需要实例
@classmethod 类方法,第一个参数是 cls
@property 把方法变成属性访问
@functools.lru_cache 自动缓存函数结果
@functools.wraps 装饰器内部使用,保留原函数元信息
@dataclasses.dataclass 自动生成 __init__

6. 关键注意点

  1. 一定要用 @functools.wraps(func),否则被装饰后的函数会丢失 __name____doc__,影响调试和文档生成。
  2. 装饰器在定义时(import 时)就执行,而不是函数调用时——所以装饰器不适合包裹“延迟执行”的逻辑。
  3. 多个装饰器的执行顺序是从下往上
    @a
    @b
    def f(): ...
    # 等价于 f = a(b(f)),b 先包裹,a 后包裹
    
  4. 装饰器也可以装饰类(如 @dataclass)甚至用类装饰整个类。

一句话总结

装饰器 = 用函数(或类)包装函数(或类),配合 @ 语法实现横切关注点(日志、缓存、鉴权、计时)与业务逻辑的解耦,是 Python 中最优雅的代码复用手段之一。