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)
因此,定义装饰器时通常需要三层结构:
- 外层函数接收被装饰的函数。
- 内层
wrapper接收原函数的参数。 - 装饰器返回
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。 - 是否返回原函数的结果。
- 是否让异常继续向调用方传播。
- 是否为有状态的装饰器考虑并发和生命周期。
- 多个装饰器叠加时,是否确认了执行顺序。
装饰器的价值在于把稳定、通用的行为从业务函数中抽离出来。先从日志和计时这类低风险场景开始,理解函数替换和闭包的关系,再逐步应用到权限、缓存和重试等场景,代码会更清晰,但也要避免为了隐藏简单逻辑而过度装饰。