Python 装饰器入门:从函数包装到可复用行为

2026-09-01 36 预计阅读时间: 1 分钟
来源: realpython.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:6 分钟

Python 装饰器是一种给函数或类附加行为的机制。它可以在不修改原函数主体的前提下,增加日志、计时、权限检查、缓存等通用逻辑,让业务代码更短,也更容易复用。

理解装饰器的关键,不是记住 @decorator 语法,而是看清它背后发生了什么:一个函数接收另一个函数,并返回一个新的函数。

装饰器的核心结构

下面是一个最小的装饰器示例:

from functools import wraps


def announce(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"开始执行:{func.__name__}")
        result = func(*args, **kwargs)
        print(f"执行完成:{func.__name__}")
        return result

    return wrapper


@announce
def greet(name):
    """向指定的人打招呼。"""
    return f"你好,{name}!"


print(greet("小林"))
print(greet.__name__)
print(greet.__doc__)

运行结果类似这样:

开始执行:greet
执行完成:greet
你好,小林!
greet
向指定的人打招呼。

@announce 是下面写法的简写:

greet = announce(greet)

因此,定义装饰器时通常需要三层结构:

  1. 外层函数接收被装饰的函数。
  2. 内层 wrapper 接收原函数的参数。
  3. 装饰器返回 wrapper

*args**kwargs 让包装函数能够兼容位置参数和关键字参数。functools.wraps 则会把原函数的名称和文档字符串复制到包装函数上,避免调试和生成文档时只看到 wrapper

装饰器如何保留原有行为

一个好的装饰器通常只负责增加行为,并且要返回原函数的结果。比如,可以用装饰器统计函数执行时间:

from functools import wraps
from time import perf_counter, sleep


def measure_time(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        started = perf_counter()
        try:
            return func(*args, **kwargs)
        finally:
            elapsed = perf_counter() - started
            print(f"{func.__name__} 耗时 {elapsed:.3f} 秒")

    return wrapper


@measure_time
def load_data():
    sleep(0.2)
    return ["python", "decorator"]


items = load_data()
print(items)

这里使用 try...finally,所以即使被装饰函数抛出异常,也能记录执行时间。装饰器不应该悄悄吞掉异常,否则调用方会很难判断操作究竟成功还是失败。

装饰器也可以接收自己的配置参数。此时需要再增加一层函数:

from functools import wraps


def repeat(times):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            result = None
            for _ in range(times):
                result = func(*args, **kwargs)
            return result

        return wrapper

    return decorator


@repeat(3)
def say(message):
    print(message)


say("执行一次")

@repeat(3) 的调用过程可以理解为:

say = repeat(3)(say)

也就是说,repeat(3) 先生成一个真正的装饰器,再由这个装饰器包装 say

可以在哪些地方使用

装饰器适合处理多个函数都会遇到、但不属于函数核心职责的逻辑,例如:

  • 记录函数调用和参数。
  • 测量执行时间。
  • 检查调用权限。
  • 在满足条件时重试操作。
  • 为结果增加缓存。
  • 统一处理输入或输出格式。

例如,下面的装饰器可以检查调用者是否具备管理员身份。这个示例假设用户信息通过关键字参数 user 传入:

from functools import wraps


def admin_only(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        user = kwargs.get("user")
        if not user or not user.get("is_admin"):
            raise PermissionError("需要管理员权限")
        return func(*args, **kwargs)

    return wrapper


@admin_only
def delete_report(report_id, *, user):
    return f"已删除报告 {report_id}"


print(delete_report("r-100", user={"name": "Ada", "is_admin": True}))

在真实项目中,权限来源、异常类型和审计记录需要根据框架和业务约定调整。装饰器也不应承载过多业务分支;如果包装逻辑已经比原函数更复杂,独立的服务或类通常更容易测试和维护。

使用时的检查清单

使用装饰器前,可以检查几个细节:

  • 是否使用 @wraps 保留原函数元数据。
  • 是否完整转发 *args**kwargs
  • 是否返回原函数的结果。
  • 是否让异常继续向调用方传播。
  • 是否为有状态的装饰器考虑并发和生命周期。
  • 多个装饰器叠加时,是否确认了执行顺序。

装饰器的价值在于把稳定、通用的行为从业务函数中抽离出来。先从日志和计时这类低风险场景开始,理解函数替换和闭包的关系,再逐步应用到权限、缓存和重试等场景,代码会更清晰,但也要避免为了隐藏简单逻辑而过度装饰。


相关推荐