装饰器(Decorator)是 Python 中最具表现力和实用性的语法特性之一,了解装饰器的用法对于个人阅读源码以及后续进行开发工作大有裨益。


1. 装饰器是什么?

Python 装饰器是一种用于增强函数或类功能的语法糖,其本质是:一个函数接收另一个函数作为参数并返回一个新的函数

一个基本的装饰器例子如下:

def decorator(func):
    def wrapper(*args, **kwargs):
        print("Before call")
        result = func(*args, **kwargs)
        print("After call")
        return result
    return wrapper

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

say_hello("Mario")

等价转换:

say_hello = decorator(say_hello)

2. Python 函数对象和闭包机制

函数是“第一类对象”(First-Class Object)

Python 中函数是对象,可以赋值、传参、嵌套、返回:

def outer():
    x = "Python"
    def inner():
        print(f"Hello {x}")  # ⬅️ 引用了外部变量 x
    return inner

f = outer()  # outer 执行完毕,x 本应销毁
f()          # 仍然能访问 x,形成闭包

这段代码体现了 闭包(Closure):inner被返回并延续生命周期,同时 inner 引用了 outer 的变量,这个 x 被“闭”在 inner 的作用域中,即使 outer 已结束。

我们可以通过 __closure__ 验证

f = outer()  # outer 执行完毕,x 本应销毁
f()  # 仍然能访问 x,形成闭包

print(f.__closure__)  # (<cell at 0x00000279D39B01F0: str object at 0x00000279CF9C2EF0>,)
print(f.__closure__[0].cell_contents)  # Python

说明 f 中确实捕获了外部作用域的变量,这是 Python 闭包的实现机制(cell 对象)。

而装饰器的本质正是闭包函数:

def decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)  # 引用了外部 func
    return wrapper

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

若我们希望通过装饰器传参,例如 @retry(max_retries=3),就必须使用三层嵌套结构

def retry(max_retries=3):  # 第1层:接收参数
    def decorator(func):        # 第2层:接收被装饰函数
        def wrapper(*args, **kwargs):  # 第3层:包装函数逻辑
            for i in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"第 {i+1} 次失败:{e}")
            raise RuntimeError("重试失败")
        return wrapper
    return decorator

使用方式:

@retry(max_retries=2)
def task():
    raise ValueError("出错了")
    
# 调用 task 才会触发装饰器逻辑
task()

这样写的时候,retry(max_retries=2) 本质上会先被调用,返回一个装饰器(即 decorator(func)),再用于装饰 task。

相当于:

# Step 1: 调用 retry(max_retries=5) → 得到 decorator
decorator = retry(2)

# Step 2: 调用 decorator(task) → 得到 wrapper
task = decorator(task)

4. functools.wraps:保持原函数信息

装饰器默认会覆盖原函数的 __name____doc__ 等元信息,解决办法是使用 functools.wraps

from functools import wraps

def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

使用 @wraps(func) 后,wrapper 将拥有 func 的原始元信息,便于调试和文档生成(如 FastAPI 的 OpenAPI)。

【注】:有关functools.wraps的介绍可移步到 浅谈Python装饰器中常用的functools.wraps


5. 类装饰器

除了函数,装饰器也可以由类实现,只需实现 __call__ 方法:

【注】:有关Python中__call__ 方法的介绍可移步到 浅谈Python中的 “call“ 方法

class Decorator:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("Before")
        return self.func(*args, **kwargs)

@Decorator
def greet():
    print("Hello")

类装饰器适合需要状态持久化的复杂装饰逻辑。


6. 多装饰器顺序解析

多个装饰器的执行顺序是从下往上包裹,但从外到内执行

@A
@B
def f(): ...
# 等价于 f = A(B(f))

例子:

def A(func):
    def wrapper(*args, **kwargs):
        print("A: before")
        result = func(*args, **kwargs)
        print("A: after")
        return result
    return wrapper

def B(func):
    def wrapper(*args, **kwargs):
        print("B: before")
        result = func(*args, **kwargs)
        print("B: after")
        return result
    return wrapper

@A
@B
def say_hello():
    print("Hello!")

say_hello()

输出结果:

A: before
B: before
Hello!
B: after
A: after

装饰器执行顺序解释:

@A
@B
def say_hello():
    print("Hello!")

相当于:

say_hello = A(B(say_hello))

所以执行链是:

  1. say_hello() 实际调用的是 A 返回的 wrapper
  2. 进入 A.wrapper:打印 “A: before”
  3. 然后在 A.wrapper 中调用 B.wrapper:打印 “B: before”
  4. 执行原始 say_hello():打印 “Hello!”
  5. 返回到 B.wrapper 中结束:打印 “B: after”
  6. 返回到 A.wrapper 中结束:打印 “A: after”

7. 装饰器使用场景

场景 示例
权限控制 @requires_login
缓存加速 @lru_cache
执行计时 @timeit
参数校验 @validate_input
日志记录 @log
注册函数 @register("op")
单例模式 @singleton
限流防抖 @throttle(2)

8. 高阶应用示例

8.1 注册工具函数(函数注册到 dict)

本质: 装饰器运行时把函数对象写入一个“注册表”。

registry = {}

def register(name):
    def decorator(func):
        registry[name] = func
        return func
    return decorator

@register("add")
def add(a, b):
    return a + b

@register("mul")
def mul(a, b):
    return a * b

print(registry["add"](2, 3))  # 输出 5

8.2 异步函数装饰器

注意:必须使用 async def wrapper(),并用 await 调用原函数:

import asyncio
from functools import wraps

def async_logger(func):
    @wraps(func)
    async def wrapper(*args, **kwargs):
        print(f"[async] 正在调用 {func.__name__}")
        result = await func(*args, **kwargs)
        print(f"[async] 调用完成")
        return result
    return wrapper

@async_logger
async def fetch_data():
    await asyncio.sleep(1)
    print("返回数据")
    return 42

asyncio.run(fetch_data())

8.3 单例模式装饰器

def singleton(cls):
    instances = {}
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance

@singleton
class DBClient:
    pass

8.4 函数计时器

import time

def timeit(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        print(f"函数 {func.__name__} 耗时 {time.time() - start:.4f}s")
        return result
    return wrapper

@timeit
def work():
    time.sleep(1)

work()

8.5 函数节流(Throttle)

import time

def throttle(interval):
    last_call = [0]
    def decorator(func):
        def wrapper(*args, **kwargs):
            now = time.time()
            if now - last_call[0] >= interval:
                last_call[0] = now
                return func(*args, **kwargs)
            else:
                print("调用太频繁,被拦截")
        return wrapper
    return decorator

@throttle(2)
def send_msg():
    print("发送消息")

send_msg()
time.sleep(1)
send_msg()
time.sleep(2)
send_msg()

9. 装饰器结构图

@A
@B
@C
def f():
    ...

执行过程是:
A → B → C → f() → C → B → A

10. 总结:装饰器核心知识点

项目 说明
装饰器是语法糖 @f == func = f(func)
装饰器是高阶函数 函数接收函数,返回函数
闭包机制支撑 内部函数引用外部函数变量
支持三层嵌套 支持参数的装饰器 @retry(max_retries=3)
保留原函数信息 使用 functools.wraps()
可类装饰 实现 __call__ 即可
多装饰器顺序 从下往上包裹,从外向内执行

后记:装饰器是一种“写一次,改行为”的强大抽象

从 Flask 路由声明、FastAPI 参数解析、权限控制、缓存机制,到大模型工具注册(如 LangChain 的 @tool),装饰器都是不可或缺的利器。

掌握装饰器,不仅能让我们的代码更优雅,也进一步为我们理解 Python 的函数式编程、元编程和框架底层打下基础。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐