
一、核心概念1. 什么是装饰器装饰器Decorator本质上是一个高阶函数——它接收一个函数作为参数并返回一个新的函数。它允许你在不修改原函数代码和调用方式的前提下为函数动态添加额外功能。2. 设计原则装饰器遵循开放-封闭原则对扩展开放允许新增功能对修改封闭不需要修改原函数内部代码3. 语法糖使用decorator_name放在函数定义上方是以下写法的简洁形式decorator def func(): pass # 等价于 def func(): pass func decorator(func)二、前置知识要理解装饰器必须先掌握两个概念1. 函数是一等公民在 Python 中函数可以像变量一样被赋值、作为参数传递、作为返回值。这是装饰器存在的基础。2. 闭包Closure在一个内部函数中对外部作用域非全局作用域的变量进行引用这个内部函数就是闭包。装饰器的核心实现正是利用了闭包的特性。三、工作原理解析def my_decorator(func): def wrapper(*args, **kwargs): # 执行前的操作 print(调用函数前) result func(*args, **kwargs) # 执行原函数 # 执行后的操作 print(调用函数后) return result return wrapper my_decorator def say_hello(name): print(fHello, {name}!) say_hello(Alice) # 输出 # 调用函数前 # Hello, Alice! # 调用函数后执行流程Python 解释器遇到my_decorator执行say_hello my_decorator(say_hello)现在say_hello实际指向wrapper函数调用时执行wrapper中的逻辑四、关键细节functools.wraps使用装饰器后原函数的元信息如__name__、__doc__会被wrapper覆盖影响调试和文档生成。解决方案使用functools.wraps。from functools import wraps def my_decorator(func): wraps(func) # 保留原函数的 __name__ 和 docstring def wrapper(*args, **kwargs): 这是 wrapper 的文档 return func(*args, **kwargs) return wrapper my_decorator def hello(): 这是 hello 的文档 pass print(hello.__name__) # 输出: hello (没有 wraps 则输出 wrapper) print(hello.__doc__) # 输出: 这是 hello 的文档五、典型案例案例 1计时装饰器性能分析快速测量函数执行时间是最常见的应用场景之一。import time import functools def timer(func): functools.wraps(func) def wrapper(*args, **kwargs): start time.perf_counter() result func(*args, **kwargs) elapsed time.perf_counter() - start print(f[TIMER] {func.__name__} 耗时: {elapsed:.4f} 秒) return result return wrapper timer def slow_function(): time.sleep(1) return 完成 slow_function() # 输出: [TIMER] slow_function 耗时: 1.0002 秒案例 2日志记录装饰器记录函数的调用信息和返回值是最常见的应用场景之一。import functools def log(func): functools.wraps(func) def wrapper(*args, **kwargs): print(f[LOG] 调用 {func.__name__}参数: {args}, {kwargs}) result func(*args, **kwargs) print(f[LOG] {func.__name__} 返回: {result}) return result return wrapper log def add(a, b): return a b add(2, 3) # 输出: # [LOG] 调用 add参数: (2, 3), {} # [LOG] add 返回: 5案例 3登录权限验证Web 开发常见检查用户登录态或角色权限是 Web 开发中的典型用法。import functools def login_required(func): functools.wraps(func) def wrapper(user, *args, **kwargs): if not user.get(is_login, False): raise PermissionError(用户未登录请先登录) return func(user, *args, **kwargs) return wrapper login_required def view_profile(user): print(f查看用户: {user[name]} 的个人资料) # 测试 user_a {name: 张三, is_login: True} user_b {name: 李四, is_login: False} view_profile(user_a) # 正常执行 # view_profile(user_b) # 抛出 PermissionError参数传递原理解析在这个代码中wrapper(user, *args, **kwargs)里的user是wrapper函数的第一个形参它不是从别处继承来的而是由调用者在调用被装饰后的函数时传入的实参。1. 装饰器语法糖展开# 下面的写法 login_required def view_profile(user): print(f查看用户: {user[name]} 的个人资料) # 等价于 def view_profile(user): print(f查看用户: {user[name]} 的个人资料) view_profile login_required(view_profile)2. 执行后的结果login_required(view_profile)执行后返回了内部定义的wrapper函数此时变量名view_profile不再指向原来的函数而是指向了wrapper这个新函数所以当你最后调用view_profile(user_a)时实际上调用的是wrapper(user_a) # 因为 wrapper 的第一个参数名叫 user所以 user_a 就传给了它3.user参数的来源总结定义来源wrapper函数定义时自己声明的第一个位置参数值来源来自于外部调用时传入的第一个实参在上例中是user_a字典4. 为什么要这样设计因为被装饰的原函数view_profile本身就需要一个user参数而装饰器wrapper为了能替换原函数并正常工作必须保持相同的调用签名即接收同样的参数然后把这些参数原封不动地传递给原函数funcwrapper(user, *args, **kwargs)负责接收所有传入参数func(user, *args, **kwargs)负责把参数传给真正的业务函数如果原函数有多个参数比如def view_profile(user, age)那么调用时age就会通过*args或**kwargs捕获并传递过去。补充说明user在这里只是一个普通的参数名你可以把它改成u或person效果完全一样只要内部一致即可。它和面向对象中的继承没有任何关系。案例 4重试机制处理网络请求在网络请求等不稳定场景中失败后自动重试。import time import functools def retry(max_attempts3, delay1): 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: if attempt max_attempts: raise e print(f第 {attempt} 次失败{delay}秒后重试...) time.sleep(delay) return None return wrapper return decorator retry(max_attempts3, delay0.5) def unstable_request(): import random if random.random() 0.7: raise ConnectionError(网络不稳定) return 请求成功 # unstable_request()代码结构总览这段代码实现了三层嵌套函数因为装饰器本身需要接收参数retry(max_attempts, delay) → 返回 decorator 函数 decorator(func) → 返回 wrapper 函数 wrapper(*args, **kwargs) → 实际执行逻辑逐层详细拆解第1层retry(max_attempts3, delay1)def retry(max_attempts3, delay1): def decorator(func): # ... return decorator作用这是装饰器的工厂函数接收配置参数返回真正的装饰器参数说明max_attempts3最大尝试次数默认3次delay1每次重试的等待秒数默认1秒关键点因为装饰器本身需要参数所以要多一层函数来接收这些参数第2层decorator(func)def decorator(func): def wrapper(*args, **kwargs): # ... return wrapper作用这是真正的装饰器接收被装饰的函数func返回包装后的函数wrapper闭包特性decorator内部可以访问外层retry的max_attempts和delay参数第3层wrapper(*args, **kwargs)def wrapper(*args, **kwargs): for attempt in range(1, max_attempts 1): try: return func(*args, **kwargs) except Exception as e: if attempt max_attempts: raise e print(f第 {attempt} 次失败{delay}秒后重试...) time.sleep(delay) return None作用包装后的函数包含重试逻辑逐行解析循环控制for attempt in range(1, max_attempts 1)attempt从 1 到max_attempts包含尝试执行try: return func(*args, **kwargs)如果成功直接return结果不继续循环异常处理捕获任何异常如果是最后一次尝试attempt max_attempts直接抛出异常raise e否则打印提示并等待delay秒后继续循环兜底返回return None理论上不会执行到这里仅作为语法完整性保留应用到具体函数retry(max_attempts3, delay0.5) def unstable_request(): import random if random.random() 0.7: raise ConnectionError(网络不稳定) return 请求成功等价于def unstable_request(): # ... 原函数代码 unstable_request retry(max_attempts3, delay0.5)(unstable_request) # 等价于 # decorator retry(max_attempts3, delay0.5) # unstable_request decorator(unstable_request)完整执行流程示例假设random.random()返回 0.5小于0.7触发异常unstable_request() # ↓ # 进入 wrapperattempt 1 # try: 执行原函数 → 抛出 ConnectionError # except: attempt1 ! 3打印 第 1 次失败0.5秒后重试... # 等待 0.5 秒 # ↓ # 进入下一轮循环attempt 2 # try: 执行原函数 → 又抛出 ConnectionError # except: attempt2 ! 3打印 第 2 次失败0.5秒后重试... # 等待 0.5 秒 # ↓ # 进入下一轮循环attempt 3 # try: 执行原函数 → 再次抛出 ConnectionError # except: attempt3 3执行 raise e抛出异常 # ↓ # 程序崩溃显示 ConnectionError: 网络不稳定如果第3次成功了假设random.random()返回 0.8# attempt 3 # try: 执行原函数 → 返回 请求成功 # return 请求成功直接退出函数 # ↓ # 最终输出请求成功关键设计思想1. 闭包Closureretry → decorator → wrapper每一层都能访问外层变量形成了闭包链。2. 参数传递路径max_attempts3, delay0.5 ↓ decorator 捕获这些参数 ↓ wrapper 使用这些参数控制重试逻辑3. 透明传递使用*args和**kwargs确保任何参数都能传递给原函数装饰器不关心具体参数。4. 异常处理策略不吞异常最后尝试失败时重新抛出原始异常保留堆栈使用raise e而不是raise但raise e会重置堆栈信息更好的做法是直接raise改进建议如果想保留完整的异常堆栈可以改为except Exception as e: if attempt max_attempts: raise # 不传 e保留完整堆栈更健壮的版本可以添加指数退避time.sleep(delay * (2 ** (attempt - 1))) # 0.5, 1.0, 2.0...总结对比组件职责参数retry配置工厂max_attempts, delaydecorator装饰器主体funcwrapper包装逻辑*args, **kwargs这种三层结构是带参数装饰器的标准写法核心思想是将配置与逻辑分离使装饰器既灵活又复用性强。案例 5缓存计算结果Memoization利用闭包保存状态避免重复计算大幅提升性能。import functools def memoize(func): cache {} functools.wraps(func) def wrapper(n): if n not in cache: cache[n] func(n) return cache[n] return wrapper memoize def fibonacci(n): if n 2: return n return fibonacci(n - 1) fibonacci(n - 2) print(fibonacci(35)) # 第一次稍慢后续极快实际项目中可直接使用内置的functools.lru_cache。案例 6带参数的装饰器日志级别控制当装饰器本身需要参数时需要三层嵌套工厂模式。import functools def log(levelINFO): def decorator(func): functools.wraps(func) def wrapper(*args, **kwargs): print(f[{level}] 调用函数: {func.__name__}) return func(*args, **kwargs) return wrapper return decorator log(levelERROR) def dangerous_operation(): print(执行危险操作) dangerous_operation() # 输出: [ERROR] 调用函数: dangerous_operation逻辑链条log(levelERROR)先执行返回decoratordecorator(dangerous_operation)执行返回wrapperdangerous_operation()实际调用的是wrapper案例 7类装饰器单例模式用类实现装饰器可以管理更复杂的状态。import functools class Singleton: def __init__(self, cls): self.cls cls self.instance None def __call__(self, *args, **kwargs): if self.instance is None: self.instance self.cls(*args, **kwargs) return self.instance Singleton class Database: def __init__(self): print(初始化数据库连接...) db1 Database() # 初始化数据库连接... db2 Database() print(db1 is db2) # True始终是同一个实例案例 8类装饰器调用计数利用__call__方法使实例可调用实现对函数调用次数的统计。import functools class CountCalls: def __init__(self, func): functools.update_wrapper(self, func) self.func func self.count 0 def __call__(self, *args, **kwargs): self.count 1 print(f{self.func.__name__} 已被调用 {self.count} 次) return self.func(*args, **kwargs) CountCalls def greet(name): return fHello, {name}! greet(Alice) greet(Bob) # 输出: # greet 已被调用 1 次 # greet 已被调用 2 次六、多个装饰器的执行顺序当多个装饰器叠加时从下往上包裹从上往下执行timer # 后执行外层后包裹先执行外层逻辑 log(levelDEBUG) # 先被包裹内层后执行内层逻辑 def add(a, b): time.sleep(0.1) return a b # 等价于: add timer(log(levelDEBUG)(add)) # 执行顺序: 先执行 log 的 wrapper 逻辑再执行 timer 的 wrapper 逻辑 # 实际输出: # [DEBUG] 调用 add参数: (3, 5), {} # [DEBUG] add 返回: 8 # [TIMER] add 耗时: 0.1005 秒七、常见应用场景场景说明关键技术日志记录自动记录函数调用信息和参数两层嵌套 wraps性能监控统计执行时间、调用次数计时装饰器权限校验检查用户登录态或角色权限带参数的装饰器重试与降级失败后自动重试或返回默认值三层嵌套工厂模式缓存减少重复计算提高响应速度闭包字典存状态事务管理数据库操作的自动提交/回滚装饰器模式限流控制函数调用频率装饰器 状态管理单例模式确保类只有一个实例类装饰器八、Python 标准库中常用的装饰器装饰器说明functools.lru_cache函数结果缓存staticmethod定义静态方法classmethod定义类方法property属性访问控制functools.singledispatch基于类型的函数重载functools.wraps保留原函数元信息九、总结装饰器的核心价值在于分离关注点核心业务逻辑保持纯粹横切关注点日志、验证、缓存、计时等通过装饰器叠加掌握装饰器需要注意的关键点本质装饰器是高阶函数接收函数返回新函数语法糖decorator只是func decorator(func)的简写规范务必使用functools.wraps(func)保留原函数属性参数处理原函数参数用*args, **kwargs处理装饰器参数需要多一层嵌套场景日志、计时、缓存、权限校验、重试机制、事务处理等横切关注点装饰器的精髓在于**开闭原则**——对扩展开放对修改封闭。当你发现多个函数需要重复编写同一段前置/后置逻辑时就是该用装饰器的时候了。掌握装饰器你的 Python 代码将从过程式脚本迈向Pythonic 的工程化代码。