装饰器开销无法消除但可控,每次调用必增一次函数调用和栈帧;functools.wraps无运行成本但影响调试;带参装饰器在导入时构造闭包;避免运行时类型检查,推荐静态检查加可关闭断言;@lru_cache可抵消甚至降低开销。

装饰器本身开销无法消除,但可控制在单次函数调用级别
装饰器必然引入一次额外函数调用,这是 Python 语言模型决定的——@decorator 等价于 func = decorator(func)。所谓“减少开销”,不是去掉这层调用,而是避免它被放大或重复触发。
- 每层装饰器都增加一个栈帧,嵌套 3 层就多 3 次函数入口/出口开销
-
functools.wraps不增加运行时成本,但不加会导致__name__、__doc__错乱,影响调试和工具链(如 pytest 收集、sphinx 文档生成) - 带参数的装饰器(如
@retry(max_tries=3))会额外执行一次闭包构造,在模块导入时就发生,与运行无关
避免在装饰器里做运行时类型检查
Python 3.12 的类型提示语法(如 list[str])本身无开销,但如果你在装饰器内部调用 isinstance(x, T) 或触发协议检查(比如 float(x)),就会激活 __instancecheck__,产生真实成本。
- 错误写法:
if not isinstance(arg, int): raise TypeError—— 这类校验放在装饰器里,每次调用都执行 - 推荐做法:只在校验必要时才做,或改用静态检查(mypy/pyright)+ 运行时断言(
assert isinstance(...),生产环境可关闭) -
TypeVar的bound定义(如T = TypeVar('T', bound=SupportsFloat))不耗性能,真正耗的是你用它做判断的地方
用 @lru_cache 抵消装饰器开销,甚至净收益
单纯计时、日志类装饰器开销微小(通常
-
@lru_cache(maxsize=128)对纯函数有效,要求所有参数可哈希 - 注意:缓存键基于
args和kwargs内容,含可变对象(如list)会报错,应先转tuple或用typed_ast类型化处理 - Python 3.12 的
lru_cache已优化哈希路径,在键复杂度高时比 3.11 快约 12%
异步装饰器必须用 async def wrapper,否则阻塞事件循环
给 async def 函数加同步装饰器(如普通 timer_decorator),会导致整个协程被当作同步函数调用,失去并发能力,实际开销可能翻倍。
立即学习“Python免费学习笔记(深入)”;
- 正确结构:装饰器内必须定义
async def wrapper,且await orig_func(...) - 错误示例:
result = orig_func(*args, **kwargs)(没 await)→ 触发RuntimeWarning: coroutine 'xxx' was never awaited - Python 3.12 的
asyncio调度器对await路径做了内联优化,但前提是装饰器不破坏协程返回类型


















