装饰器是Python高阶函数与语法糖的核心应用,也是框架底层(Flask/Django/FastAPI)最常见的代码设计模式。它可以在不修改原函数代码、不改动调用方式的前提下,动态增强函数功能,完美符合开闭原则。
本文全覆盖高阶装饰器重难点:基础装饰器回顾、带参数装饰器、类装饰器、functools.wraps元数据保留、多层装饰器链执行顺序。全程搭配可落地实战代码、底层原理拆解、工程高频坑点、面试真题,一次性吃透Python所有装饰器进阶用法。
一、装饰器核心基础回顾
1.1 本质与核心特性
装饰器本质:接收函数为参数、返回新函数的高阶函数。
核心优势:
1、开闭原则:对扩展开放,对修改关闭,不侵入原业务代码;
2、逻辑复用:日志、计时、权限、重试、缓存等通用逻辑统一抽离;
3、代码解耦:业务逻辑与通用切面逻辑分离,代码更整洁。
1.2 无参基础装饰器标准模板
import time
# 通用计时装饰器(无参)
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print(f"函数{func.__name__}执行耗时:{end - start:.2f}s")
return res
return wrapper
@timer
def test_func():
time.sleep(1)
print("业务函数执行完成")
test_func()
语法糖@timer 等价于 test_func = timer(test_func),在函数定义时完成装饰替换。
二、带参数装饰器(高阶工程必备)
基础装饰器只能固定增强逻辑,带参数装饰器可以在装饰器传参,实现动态配置、灵活适配不同业务场景,是项目中最常用的高阶写法。
核心结构:三层嵌套函数
1、外层函数:接收装饰器参数,返回真正的装饰器;
2、中层函数:接收原函数,是标准装饰器;
3、内层函数:执行增强逻辑与原函数。
2.1 带参数装饰器实战:自定义重试次数
import time
# 带参数装饰器:参数为最大重试次数
def retry(max_times):
# 外层:接收装饰器参数
def decorator(func):
# 中层:接收目标函数
def wrapper(*args, **kwargs):
for i in range(max_times):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"第{i+1}次执行失败,异常:{e},准备重试")
time.sleep(1)
raise Exception("多次重试全部失败")
return wrapper
return decorator
# 使用:给装饰器传参,指定重试3次
@retry(max_times=3)
def risky_task():
# 模拟随机报错
import random
if random.random() > 0.3:
raise ValueError("随机业务异常")
print("任务执行成功")
risky_task()
2.2 带参装饰器执行逻辑
@retry(3) 执行流程:
1、先执行 retry(3),返回内部 decorator 装饰器函数;
2、再执行标准装饰器逻辑 risky_task = decorator(risky_task);
3、调用函数时执行 wrapper 增强逻辑。
三、functools.wraps 保留函数元数据(必写规范)
默认装饰器会替换原函数元数据:函数名、文档注释、参数签名全部变为内层wrapper函数信息,导致日志打印、接口文档、调试信息错乱,是工程高频坑点。
wraps 作用:将原函数的元数据拷贝到装饰器内层函数,伪装保留原函数属性。
3.1 无wraps的问题演示
def timer(func):
def wrapper(*args, **kwargs):
"""装饰器包装函数"""
return func(*args, **kwargs)
return wrapper
@timer
def business_func():
"""业务核心函数"""
pass
# 元数据被篡改
print(business_func.__name__) # 输出 wrapper
print(business_func.__doc__) # 输出装饰器注释
3.2 标准wraps修复方案(工程强制规范)
from functools import wraps
def timer(func):
# 保留原函数所有元数据
@wraps(func)
def wrapper(*args, **kwargs):
"""装饰器包装函数"""
return func(*args, **kwargs)
return wrapper
@timer
def business_func():
"""业务核心函数"""
pass
# 元数据正常保留
print(business_func.__name__) # 输出 business_func
print(business_func.__doc__) # 输出业务核心函数注释
工程铁律:自定义所有函数装饰器,内层wrapper必须加 @wraps(func)。
四、类装饰器(高阶进阶)
除了函数装饰器,Python支持类装饰器,通过 __call__ 魔法方法 实现。适合状态保存、参数复杂、生命周期管理的装饰器场景(如接口限流、统计计数、缓存过期)。
核心原理:类实例可被调用(__call__),实例初始化接收原函数,调用时执行增强逻辑。
4.1 基础类装饰器实战:函数调用计数
from functools import wraps
class CountDecorator:
def __init__(self, func):
# 初始化:接收被装饰函数
self.func = func
self.count = 0 # 实例保存状态:调用次数
# 实例可被调用
@wraps(func)
def __call__(self, *args, **kwargs):
self.count += 1
print(f"函数第{self.count}次调用")
return self.func(*args, **kwargs)
@CountDecorator
def hello():
print("hello Python")
hello()
hello()
hello()
4.2 带参数类装饰器
类装饰器也支持传参,只需在外层多一层初始化,实现参数配置。
from functools import wraps
class LimitDecorator:
# 第一步:接收装饰器参数
def __init__(self, limit_num):
self.limit_num = limit_num
self.count = 0
# 第二步:接收被装饰函数
def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
if self.count >= self.limit_num:
raise Exception("函数调用次数超限")
self.count += 1
return func(*args, **kwargs)
return wrapper
# 限制最多调用3次
@LimitDecorator(limit_num=3)
def test():
print("执行函数")
test()
test()
test()
# test() # 第四次报错超限
4.3 类装饰器适用场景
1、需要保存状态的装饰器(次数统计、限流计数、缓存数据);
2、逻辑复杂、需要拆分多个方法的增强功能;
3、需要初始化配置、生命周期管理的场景。
五、装饰器链(多层装饰器叠加)
项目中一个函数可能叠加多个装饰器(日志+计时+权限+重试),形成装饰器链,其执行顺序是面试高频重难点。
5.1 核心执行口诀
装饰器加载:从上到下
函数执行:从下到上
简单总结:由外到内包装,由内到外执行
5.2 装饰器链顺序实战验证
from functools import wraps
def decorator_A(func):
@wraps(func)
def wrapper():
print("A前置逻辑")
func()
print("A后置逻辑")
return wrapper
def decorator_B(func):
@wraps(func)
def wrapper():
print("B前置逻辑")
func()
print("B后置逻辑")
return wrapper
# 多层装饰器叠加
@decorator_A
@decorator_B
def task():
print("核心业务逻辑")
task()
5.3 输出结果与顺序解析
执行输出:
A前置逻辑 → B前置逻辑 → 核心业务逻辑 → B后置逻辑 → A后置逻辑
原理:
1、加载顺序:先B装饰、后A装饰,A包裹B;
2、执行顺序:外层A先前置,内层B执行,最后依次后置收尾。
六、工程高频坑点汇总
1、丢失元数据:不写wraps导致函数名、注释错乱,调试、接口生成异常;
2、传参混淆:无参装饰器不能加括号,带参装饰器必须传参,否则报错;
3、装饰器链顺序错误:权限、日志、计时装饰器顺序错乱导致逻辑异常;
4、类装饰器状态污染:类装饰器全局唯一实例,不同函数共用计数状态,需注意隔离;
5、装饰器重复执行:装饰器在函数定义阶段加载,而非调用阶段。
七、高频面试真题汇总
题目1:装饰器的作用和原理?
答案:装饰器基于高阶函数实现,在不修改原函数代码和调用方式的前提下动态增强函数功能,遵循开闭原则,常用于日志、计时、权限、重试、缓存等通用逻辑抽离。
题目2:为什么要用 functools.wraps?不加会有什么问题?
答案:装饰器会替换原函数为内层wrapper函数,导致函数名、文档、签名等元数据丢失错乱;@wraps 拷贝原函数元数据,保留函数原始信息,是工程标准规范。
题目3:带参数装饰器的执行流程?
答案:三层嵌套结构,先执行外层函数接收装饰器参数,返回标准装饰器,再执行二层装饰器绑定原函数,最后调用时执行内层增强逻辑。
题目4:类装饰器的实现原理与适用场景?
答案:通过 __call__ 魔法方法让类实例可调用,初始化接收函数/参数,适合需要保存状态、复杂配置、计数限流等场景,弥补函数装饰器无状态的短板。
题目5:多层装饰器的加载和执行顺序?
答案:加载顺序自上而下,执行顺序自下而上;外层先包装、后执行前置,内层先执行业务、后收尾后置逻辑。
八、全文终极背诵总结
1、基础装饰器:高阶函数实现,无侵入增强函数,语法糖简化调用,符合开闭原则。
2、带参数装饰器:三层嵌套结构,支持动态传参配置,适配灵活多变的业务增强需求。
3、wraps元数据:强制工程规范,解决装饰器篡改函数信息问题,保证调试、文档正常。
4、类装饰器:基于__call__实现,支持状态保存,适合计数、限流、缓存等有状态增强场景。
5、装饰器链:从上到下加载,从下到上执行,多层叠加实现多维度功能增强。
6、工程选型:简单无状态增强用函数装饰器,有状态、复杂配置用类装饰器,多层叠加严格控制顺序。