Python 开发技巧 · 高级装饰器 —— 从基础到工业级实战

📌 知识点简介

装饰器是 Python 最优雅的特性之一,但很多开发者停留在 @timer 这种基础用法。今天深入两层:带参数的装饰器类装饰器,封装几个生产环境可直接复用的工具函数。


🔧 核心代码

1. 带参数的装饰器(三层嵌套)
复制代码

from functools import wraps import time import logging logger = logging.getLogger(__name__) def retry(max_attempts=3, delay=1, exceptions=(Exception,)): """可配置重试装饰器""" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for attempt in range(1, max_attempts + 1): try: return func(*args, **kwargs) except exceptions as e: if attempt == max_attempts: raise logger.warning( f"{func.__name__} 第 {attempt} 次失败: {e}," f"{delay}s 后重试..." ) time.sleep(delay) return None # 不会执行到这里,仅供类型检查 return wrapper return decorator def timed(unit="s", precision=4): """可配置单位的计时装饰器""" scale = {"s": 1, "ms": 1000, "μs": 1_000_000} def decorator(func): @wraps(func) def wrapper(*args, **kwargs): start = time.perf_counter() result = func(*args, **kwargs) elapsed = (time.perf_counter() - start) * scale.get(unit, 1) print(f"[⏱] {func.__name__} 耗时: {elapsed:.{precision}f} {unit}") return result return wrapper return decorator

2. 类装饰器(带状态)
复制代码

class CountCalls: """统计函数调用次数的类装饰器""" def __init__(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) 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

3. 装饰器堆叠 & functools.wraps 的重要性
复制代码

def debug_info(func): """打印传入/传出参数的调试装饰器""" @wraps(func) def wrapper(*args, **kwargs): args_repr = [repr(a) for a in args] kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()] signature = ", ".join(args_repr + kwargs_repr) print(f"[🔍] 调用: {func.__name__}({signature})") result = func(*args, **kwargs) print(f"[🔍] 返回: {result!r}") return result return wrapper # 堆叠使用(执行顺序从下往上,从里到外) @timed(unit="ms") @debug_info def fetch_user(user_id: int) -> dict: return {"id": user_id, "name": "Alice"}


🧪 使用示例

复制代码

# ---- 重试装饰器 ---- @retry(max_attempts=3, delay=0.5, exceptions=(ConnectionError, TimeoutError)) def fetch_data(url: str) -> str: # 模拟不稳定网络请求 import random if random.random() < 0.7: raise ConnectionError("网络波动") return f"data from {url}" res = fetch_data("https://api.example.com/data") print(res) # ---- 计时装饰器 ---- @timed(unit="ms") def heavy_compute(n: int) -> int: return sum(i * i for i in range(n)) heavy_compute(10_000_000) # ---- 计数装饰器 ---- @CountCalls def greet(name: str) -> str: return f"Hello, {name}!" print(greet("Alice")) # [📊] greet 已调用 1 次 print(greet("Bob")) # [📊] greet 已调用 2 次 # ---- 单例装饰器 ---- @Singleton class Database: def __init__(self, host: str): self.host = host db1 = Database("localhost") db2 = Database("remote") # 不会重新初始化 print(db1 is db2) # True print(db1.host) # "localhost"(首次创建的值) # ---- 函数元数据保留 ---- @debug_info def simple_add(a: int, b: int) -> int: """将两个数相加""" return a + b # 没有 @wraps 的话,这里会显示 wrapper 的信息 print(f"函数名: {simple_add.__name__}") # simple_add print(f"文档: {simple_add.__doc__}") # 将两个数相加


⚠️ 注意事项 / 避坑指南

坑点 说明 解决
元数据丢失 不加 @wraps(func) 会导致 __name____doc____annotations__ 全被覆盖 每个 wrapper 上务必加 @wraps(func)
多层装饰器顺序 @A @B 等价于 A(B(func)),A 在最外层 把耗时/日志放外面,重试/缓存放里面
类装饰器的 __init__ 签名 class-based 的 __init__ 接收的是函数,容易和类本身的 __init__ 混淆 用函数装饰器做复杂场景,类装饰器做简单计数/单例
装饰器参数类型检查 三层嵌套的装饰器容易把参数写混淆 善用 functools.partial 简化,或是使用 @dataclass 包装配置
性能开销 每个装饰器调用都增加函数调用栈 生产环境适量使用,高频路径避免多层装饰器堆叠
简化带参装饰器的技巧(减少嵌套)
复制代码

from functools import partial def retry(func=None, *, max_attempts=3, delay=1): """同时支持 @retry 和 @retry(max_attempts=5) 两种用法""" if func is None: return partial(retry, max_attempts=max_attempts, delay=delay) @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 time.sleep(delay) return None return wrapper # 无括号用法 @retry def api_call_1(): ... # 带参数用法 @retry(max_attempts=5, delay=2) def api_call_2(): ...


📝 总结

装饰器是 Python 的"语法糖核弹"------用得好,代码复用率和可读性暴涨;用不好,层层嵌套让调试想哭。掌握带参装饰器、类装饰器、wraps 保护元数据、以及 partial 简化嵌套这四个要点,日常开发中无论是埋点、缓存、重试还是权限校验,都能信手拈来。

相关推荐
纸小铭1 小时前
[MAF预定义ChatClient中间件-01]LoggingChatClient——在调用LLM前后输出日志
python·中间件·flask
aiqianji1 小时前
有哪些支持长文的AI生成短篇小说软件?
人工智能·python
狗都不学爬虫_2 小时前
AI逆向 - 某定制瑞树6纯算(ck-header-params后缀)
爬虫·python·网络爬虫
三十岁老牛再出发2 小时前
07.08.&07.09.每日总结
c语言·python
齐 飞2 小时前
Python常用语法总结
python
齐 飞2 小时前
LangGraph快速入门-03节点与边
python·langchain
_Jimmy_2 小时前
Python 多线程使用和场景
python
liulilittle2 小时前
Exhaustive drift-fix simulation V2 — KCC on shared-bottleneck wired path.
开发语言·网络·python·tcp/ip·计算机网络·信息与通信·通信
梦想的初衷~2 小时前
【完整教程】基于Python的无人机多光谱-点云融合生态三维建模:从Metashape重建到碳储量/生物量/LULC预测
python·无人机·三维重建·多光谱·无人机遥感·碳储量·生态建模