Python装饰器是一种基于函数是一等公民特性的语法糖,它允许我们在不修改原始函数定义的情况下,动态地为函数附加额外行为。理解装饰器,核心是要明白函数可以被当作参数传递、被返回,以及闭包如何捕获外部作用域的变量。

一、Python装饰器的底层原理
装饰器的本质是一个接收函数作为参数并返回新函数的高阶函数。当我们使用@decorator语法时,Python解释器会将下方函数传入装饰器,并用返回的对象替换原函数名。这一过程在模块加载阶段就已经完成,而不是在每次调用时才发生。
要理解装饰器,必须先理解闭包。闭包是指内部函数引用了外部函数作用域中的变量,并且外部函数已经返回,但那些变量依然被内部函数持有。装饰器正是利用闭包来保存被包装的原函数引用,从而在新函数中调用它并插入前置或后置逻辑。
def simple_decorator(func):
def wrapper(*args, **kwargs):
print("调用前逻辑")
result = func(*args, **kwargs)
print("调用后逻辑")
return result
return wrapper
@simple_decorator
def say_hello(name):
print("你好, " + name)
say_hello("小明")
# 输出:
# 调用前逻辑
# 你好, 小明
# 调用后逻辑
上面代码中,simple_decorator接收say_hello函数,返回wrapper。@语法等价于执行了say_hello = simple_decorator(say_hello)。此后调用say_hello实际执行的是wrapper,原函数在闭包中被保存为变量func。
如果被装饰的函数需要保留原有元信息(如函数名、文档字符串),应使用functools模块中的wraps装饰器,否则外部获取到的是wrapper的信息,不利于调试和文档生成。
from functools import wraps
def better_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
二、带参数的装饰器
有时我们希望装饰器本身可以接收配置参数,例如指定日志级别或超时时间。这时需要再包一层函数:最外层接收装饰器参数,中间层接收被装饰函数,最内层才是真正的包装逻辑。
这种三层嵌套结构初看复杂,但拆解后很清晰。外层函数返回真正的装饰器,真正的装饰器再返回wrapper。它适合将运行配置与横切逻辑解耦,让同一个装饰器在不同业务模块表现不同行为。
def repeat_times(times):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
func(*args, **kwargs)
return wrapper
return decorator
@repeat_times(3)
def greet():
print("执行一次")
greet()
# 连续输出三次:执行一次
三、典型应用场景说明
装饰器最常见的用途是日志与性能统计。在Web接口或算法函数中,我们往往不想在每个函数里手写计时代码,而是用装饰器统一收集执行耗时,既保持业务干净,也方便后续接入监控系统。
另一个高频场景是权限校验。在后台系统中,许多接口要求登录或特定角色,如果逐个函数写判断会非常冗余。使用装饰器可以把鉴权逻辑集中管理,未通过就直接返回错误响应。
import time
def time_cost(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
print(func.__name__ + " 耗时: " + str(time.time() - start))
return res
return wrapper
@time_cost
def slow_add(a, b):
time.sleep(0.5)
return a + b
print(slow_add(1, 2))
缓存也是装饰器的经典用法。对于计算成本高且输入重复率高的函数,可以用装饰器把结果按参数缓存起来,下次相同调用直接返回,显著降低响应时间。Python标准库的lru_cache就是此类实现。
此外,事务管理、异常重试、接口限流等都可以用装饰器优雅完成。它的价值不在于少写几行,而在于把通用逻辑从业务代码中抽离,提升可读性和可测试性。
| 场景 | 解决的问题 | 优势 |
|---|---|---|
| 日志统计 | 重复编写记录代码 | 统一入口,降低侵入 |
| 权限校验 | 散落的判断语句 | 集中控制,安全易审 |
| 结果缓存 | 重复计算开销 | 提升性能,代码透明 |
四、使用装饰器的注意事项
装饰器在导入时执行,因此不要在其中写耗时操作或依赖运行时才确定的上下文。如果装饰器内部出现错误,会导致整个模块加载失败,影响面较大。
多个装饰器叠加时,执行顺序是从下往上包装、从上往下调用。理解这一点能避免逻辑顺序错乱,比如先鉴权再计时会和先计时再鉴权在统计结果上有差异。
建议将装饰器放在单独工具模块中,并通过明确命名表达意图,例如require_login、cache_result,让阅读者一眼看懂函数被附加了什么行为。