Python 装饰器

在 Python 中,装饰器(Decorator) 是一种极其强大且优雅的设计模式。它的核心作用是:在不修改原函数代码和调用方式的前提下,动态地为函数(或类)增加额外的功能。

通俗地说,就像给房子(原函数)加装防盗门或智能锁(新功能),房子本身的结构没变,但安全性提升了,而且住户(调用者)开门的方式也没变。

下面我将从底层原理标准模板进阶用法实际场景,为你系统梳理 Python 装饰器。


1. 核心原理:闭包 + 高阶函数

装饰器本质上是一个高阶函数(接收函数作为参数,并返回一个新函数)。它利用了 Python 的闭包特性。

纯文本
# 1. 定义一个装饰器(本质是函数)
def my_decorator(func):
    # 2. 定义内部函数(闭包),接收原函数的参数
    def wrapper(*args, **kwargs):
        print("👉 [执行前] 准备调用函数...")

        # 3. 调用原函数,并保存结果
        result = func(*args, **kwargs)

        print("👈 [执行后] 函数调用完毕!")
        return result  # 返回原函数的结果

    # 4. 返回内部函数(注意:没有括号,返回的是函数对象本身)
    return wrapper

# 5. 使用装饰器(语法糖 @)
@my_decorator
def say_hello(name):
    print(f"Hello, {name}!")

# 调用时,实际执行的是 wrapper("Alice")
say_hello("Alice")

输出:

纯文本
👉 [执行前] 准备调用函数...
Hello, Alice!
👈 [执行后] 函数调用完毕!

注:@my_decorator 只是 say_hello = my_decorator(say_hello) 的语法糖。


2. ⚠️ 必须掌握的标准模板:@wraps

上面的基础写法有一个致命缺陷:原函数的元信息(如函数名 __name__、文档字符串 __doc__)会被 wrapper 覆盖,这会导致调试困难和某些依赖元信息的库(如 FastAPI、Flask)报错。

✅ 标准做法:始终使用 functools.wraps

纯文本
import functools
import time

def timer_decorator(func):
    @functools.wraps(func)  # 👈 关键:保留原函数的元信息
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"⏱️ 函数 '{func.__name__}' 耗时: {end - start:.4f} 秒")
        return result
    return wrapper

@timer_decorator
def heavy_task():
    """这是一个耗时任务"""
    time.sleep(0.5)

heavy_task()
print("函数名:", heavy_task.__name__)  # 输出: heavy_task (如果没有 @wraps,会输出 wrapper)
print("文档:", heavy_task.__doc__)     # 输出: 这是一个耗时任务

3. 进阶用法

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

如果你希望装饰器本身也能接收参数(例如:指定重试次数、日志级别),你需要三层函数嵌套

  • 第一层:接收装饰器的参数。
  • 第二层:接收被装饰的函数。
  • 第三层:wrapper,接收原函数的参数。
纯文本
def retry(max_attempts: int = 3):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"❌ 第 {attempt} 次尝试失败: {e}")
                    if attempt == max_attempts:
                        raise  # 达到最大次数,抛出异常
        return wrapper
    return decorator

@retry(max_attempts=3)
def connect_to_db():
    print("尝试连接数据库...")
    raise ConnectionError("网络超时")

# connect_to_db() # 取消注释运行,会重试 3 次后抛出异常

② 多个装饰器的执行顺序(洋葱模型)

当一个函数被多个装饰器修饰时,装饰的顺序是从下到上,执行的顺序是从外到内

纯文本
def decorator_a(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("A: 进入")
        result = func(*args, **kwargs)
        print("A: 退出")
        return result
    return wrapper

def decorator_b(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("B: 进入")
        result = func(*args, **kwargs)
        print("B: 退出")
        return result
    return wrapper

@decorator_a
@decorator_b
def my_func():
    print(">>> 核心逻辑执行 <<<")

my_func()

输出解析

纯文本
A: 进入       # 1. decorator_a 的 wrapper 开始
B: 进入       # 2. decorator_b 的 wrapper 开始
>>> 核心逻辑执行 <<< # 3. 原函数执行
B: 退出       # 4. decorator_b 的 wrapper 结束
A: 退出       # 5. decorator_a 的 wrapper 结束

(等价于 decorator_a(decorator_b(my_func)))

③ 装饰类 (Class Decorator)

装饰器不仅可以装饰函数,还可以装饰类(常用于实现单例模式或为类的所有方法添加功能)。

纯文本
def singleton(cls):
    """单例模式装饰器"""
    instances = {}
    @functools.wraps(cls)
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance

@singleton
class DatabaseConnection:
    def __init__(self):
        print("初始化数据库连接...")

db1 = DatabaseConnection() # 输出: 初始化数据库连接...
db2 = DatabaseConnection() # 无输出,直接返回 db1
print(db1 is db2)          # 输出: True

4. 实际工程中的高频应用场景

  1. 权限校验:在 Web 框架(如 Flask/FastAPI)中,检查用户是否登录或是否有特定角色。
  2. 日志记录:自动记录函数的输入参数、返回值和执行时间(如上面的 timer_decorator)。
  3. 缓存 (Memoization):Python 内置了 @functools.lru_cache@functools.cache,自动缓存函数的返回值,避免重复计算(极其适合递归或昂贵的 I/O 操作)。
纯文本
   from functools import lru_cache

   @lru_cache(maxsize=128)
   def fibonacci(n):
       if n < 2: return n
       return fibonacci(n-1) + fibonacci(n-2)
  1. 事务管理:在数据库操作中,自动处理 commitrollback

5. 装饰器的最佳实践与避坑指南

  1. 始终使用 @functools.wraps:这是专业 Python 代码的标志。
  2. 保持装饰器轻量:装饰器内部的逻辑应该尽量简单。如果逻辑复杂,应该将其拆分为独立的辅助函数。
  3. 不要滥用:如果一个功能只能通过装饰器实现,但导致代码难以理解(例如过度嵌套的带参数装饰器),不如直接在函数内部调用一个普通的辅助函数。
  4. 注意异步函数:如果你要装饰 async def 定义的异步函数,你的 wrapper 也必须是 async def,或者使用专门的异步装饰器库(如 asyncer)。
纯文本
   import functools
   def async_timer(func):
       @functools.wraps(func)
       async def wrapper(*args, **kwargs): # 👈 必须是 async
           start = time.time()
           result = await func(*args, **kwargs) # 👈 必须 await
           print(f"Async 耗时: {time.time() - start}")
           return result
       return wrapper

装饰器是 Python 迈向“高级”的必经之路。你可以尝试自己写一个 “如果函数执行时间超过 N 秒,就打印警告” 的装饰器来练手。如果有写不出来的地方,或者想了解某个具体框架(如 FastAPI)中的装饰器用法,随时告诉我!