Python高级应用系列(一):装饰器的深度实践——从入门到进阶

张开发
2026/4/11 17:34:57 15 分钟阅读

分享文章

Python高级应用系列(一):装饰器的深度实践——从入门到进阶
前言装饰器Decorator是 Python 最具代表性的语言特性之一。表面上它只是给函数加一层包装的语法糖实际上它背后藏着 Python 元编程的核心能力。很多人用装饰器只是staticmethod、property这类内置的但真正的威力在于自定义装饰器——尤其是支持参数、带状态、可堆叠的那种。本篇文章我们把装饰器从会用推进到用好覆盖以下核心内容装饰器的基础运行机制闭包视角带参数的装饰器工厂带状态的装饰器类装饰器functools.wraps的正确姿势多个装饰器的执行顺序实际生产场景重试、限流、注册、缓存类方法装饰器的特殊处理一、装饰器到底是什么先来看一个最朴素的装饰器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 调用后等价于say_hello my_decorator(say_hello)装饰器就是一个接收函数并返回新函数的函数。这层转换发生在定义时而非调用时。二、用闭包视角理解装饰器很多教程只告诉你装饰器就是函数包装但理解不深的人会在遇到复杂装饰器时一头雾水。关键在于闭包Closuredef outer(x): # 外层函数 def inner(func): # 中层函数装饰器 def wrapper(*args, **kwargs): print(f[{x}] 开始执行) return func(*args, **kwargs) return wrapper return inner三层嵌套每层都记住了外层的变量。装饰器工厂返回的是innerinner返回wrapperwrapper闭包捕获了x。outer(DEBUG) def process(): pass等价于process outer(DEBUG)(process)。三、装饰器工厂带参数的装饰器如果装饰器本身需要配置参数就需要装饰器工厂——在装饰器外面再包一层。3.1 最简单的参数化def repeat(times): def decorator(func): def wrapper(*args, **kwargs): for _ in range(times): result func(*args, **kwargs) return result return wrapper return decorator repeat(3) def greet(name): print(f你好{name}) greet(张三) # 输出你好张三连续3次3.2 带可选参数的装饰器很多场景需要装饰器同时支持有参数和无参数调用import functools import time def log(levelINFO): 支持 log 或 log(levelDEBUG) def decorator(func): functools.wraps(func) def wrapper(*args, **kwargs): start time.time() print(f[{level}] 调用 {func.__name__} ...) result func(*args, **kwargs) elapsed time.time() - start print(f[{level}] {func.__name__} 完成耗时 {elapsed:.3f}s) return result return wrapper # 如果传入的是函数而非字符串说明是 log 这样无参数调用 if callable(level): func level level INFO return decorator(func) return decorator log def func_a(): pass log(DEBUG) def func_b(): pass四、functools.wraps——必须养成的习惯不写functools.wraps(func)的装饰器会让原函数的元信息全部丢失# ❌ 不使用 wraps def bad_decorator(func): def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper bad_decorator def add(a, b): 两数相加 return a b print(add.__name__) # wrapper ——元信息丢失 print(add.__doc__) # None# ✅ 使用 wraps import functools def good_decorator(func): functools.wraps(func) # 这一行必须加 def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper good_decorator def add(a, b): 两数相加 return a b print(add.__name__) # add print(add.__doc__) # 两数相加元信息在装饰器中丢失会直接影响调试时打印堆栈看不到原函数名inspect.signature()获取签名Sphinx / pdoc 自动生成文档五、装饰器执行顺序就近优先多个装饰器叠加时从近到远依次执行类似从下到上堆叠def deco_a(func): def wrapper(*args, **kwargs): print(A 进入) result func(*args, **kwargs) print(A 退出) return result return wrapper def deco_b(func): def wrapper(*args, **kwargs): print(B 进入) result func(*args, **kwargs) print(B 退出) return result return wrapper deco_a deco_b def target(): print(target 执行) target()输出A 进入 B 进入 target 执行 B 退出 A 退出六、类装饰器带状态的装饰器类装饰器可以持有状态适合需要计数器“缓存”注册表等场景6.1 注册器装饰器class Registry: def __init__(self): self._registry {} def register(self, nameNone): 装饰器工厂registry.register(my_name) def decorator(func): key name or func.__name__ self._registry[key] func return func return decorator def get(self, name): return self._registry.get(name) def list_all(self): return list(self._registry.keys()) # 全局注册表 router Registry() router.register(home) def home_page(): return 首页 router.register(about) def about_page(): return 关于页 router.register(contact) def contact_page(): return 联系页 # 动态调用 print(router.list_all()) # [home, about, contact] print(router.get(home)()) # 首页6.2 带计数的装饰器import functools class CallCounter: def __init__(self): self._counts {} def __call__(self, func): functools.wraps(func) def wrapper(*args, **kwargs): self._counts[func.__name__] self._counts.get(func.__name__, 0) 1 return func(*args, **kwargs) # 暴露计数器属性方便外部查询 wrapper.call_count lambda: self._counts[func.__name__] return wrapper def report(self): return dict(self._counts) counter CallCounter() counter def fetch_data(): pass counter def save_data(): pass fetch_data() fetch_data() save_data() print(counter.report()) # {fetch_data: 2, save_data: 1}七、生产场景实战7.1 重试装饰器import functools import time import logging logger logging.getLogger(__name__) def retry(max_attempts3, delay1, exceptions(Exception,)): 指数退避重试装饰器 def decorator(func): functools.wraps(func) def wrapper(*args, **kwargs): attempts 0 while attempts max_attempts: try: return func(*args, **kwargs) except exceptions as e: attempts 1 if attempts max_attempts: logger.error(f{func.__name__} 重试{max_attempts}次后仍失败: {e}) raise wait delay * (2 ** (attempts - 1)) # 指数退避 logger.warning(f{func.__name__} 失败{wait}s后重试第{attempts}次...) time.sleep(wait) return wrapper return decorator retry(max_attempts3, delay1, exceptions(ConnectionError, TimeoutError)) def call_api(url): # 模拟网络不稳定 import random if random.random() 0.7: raise ConnectionError(网络错误) return API响应数据7.2 速率限制装饰器import time import functools from threading import Lock def rate_limit(calls_per_second): 令牌桶限流装饰器 min_interval 1.0 / calls_per_second lock Lock() last_called 0.0 def decorator(func): functools.wraps(func) def wrapper(*args, **kwargs): nonlocal last_called with lock: elapsed time.time() - last_called if elapsed min_interval: time.sleep(min_interval - elapsed) last_called time.time() return func(*args, **kwargs) return wrapper return decorator rate_limit(calls_per_second5) def process_item(item): print(f处理: {item}) for i in range(10): process_item(i)7.3 缓存装饰器带TTLimport functools import time def memoize(ttl60): 带过期时间的缓存装饰器 cache {} def decorator(func): functools.wraps(func) def wrapper(*args, **kwargs): key (args, tuple(sorted(kwargs.items()))) now time.time() if key in cache: result, timestamp cache[key] if now - timestamp ttl: print(f[缓存命中] {func.__name__}) return result result func(*args, **kwargs) cache[key] (result, now) return result return wrapper return decorator memoize(ttl5) def expensive_computation(n): time.sleep(1) # 模拟耗时操作 return n * n print(expensive_computation(10)) # 第一次无缓存 print(expensive_computation(10)) # 5秒内第二次命中缓存八、类方法装饰器注意事项类方法装饰器需要注意self参数的处理import functools def log_method(func): functools.wraps(func) def wrapper(self, *args, **kwargs): print(f调用方法: {func.__name__}) return func(self, *args, **kwargs) return wrapper class Calculator: def __init__(self, precision2): self.precision precision log_method # 实例方法装饰器 def add(self, a, b): return round(a b, self.precision) classmethod log_method # 类方法 装饰器 def info(cls): return fCalculator 类精度: {cls.precision}) calc Calculator(precision4) print(calc.add(3.14159, 2.71828))九、装饰器 vs 类组合有时候类组合比装饰器更灵活class Timer: def __init__(self, func): self._func func functools.update_wrapper(self, func) def __call__(self, *args, **kwargs): import time start time.time() result self._func(*args, **kwargs) print(f{self._func.__name__} 耗时 {time.time() - start:.4f}s) return result Timer def slow_function(): time.sleep(0.5) return 42十、常见陷阱总结陷阱后果解决方案不加functools.wraps__name__、__doc__丢失养成习惯必加装饰器修改了原函数签名类型检查工具、静态检查报错用functools.wraps 保持签名一致装饰器意外捕获了全局变量并发/多线程场景出问题显式传入参数避免隐式依赖装饰器在模块加载时执行副作用循环依赖、初始化顺序问题避免在装饰器顶层执行 I/O对异步函数使用同步装饰器异步行为被破坏用functools.wraps 判断asyncio.iscoroutinefunction结语装饰器是 Python 中一等公民函数最典型的应用体现。它不是炫技而是一种优雅的横切关注点分离方式——日志、重试、限流、缓存、权限校验这些逻辑如果全部写在业务函数里代码将无法维护。掌握好装饰器意味着你能写出既简洁又可扩展的 Python 代码。下一篇文章我们将进入另一个元编程核心——元类Metaclass敬请期待。系列导航← 序章Python高级特性概览一装饰器的深度实践← 当前二元类Python面向对象的暗物质三描述符与属性控制四上下文管理器与 with 语句五生成器与协程的精髓……如果这篇文章对你有帮助欢迎点赞、收藏有问题欢迎在评论区交流。

更多文章