装饰器替换函数后元数据丢失是因为新函数对象覆盖了原函数的__name__、__doc__等属性;functools.wraps通过update_wrapper复制7个关键属性并设置__wrapped__,必须直接作用于最内层wrapper函数才能生效。

装饰器替换函数后元数据为什么丢失
装饰器本质是用新函数(比如 wrapper)替代原函数对象。Python 中函数的 __name__、__doc__、__annotations__ 等属性都绑定在函数对象上,一旦被替换,这些属性自然就变成新函数的——不是“丢了”,而是被覆盖了。
常见错误现象包括:help(my_func) 显示的是 wrapper 的文档,inspect.signature(my_func) 返回的参数签名和原函数不一致,Flask 路由调试时看到函数名是 wrapper 而不是真实视图名。
functools.wraps 实际做了什么
functools.wraps 不是魔法,它只是调用 functools.update_wrapper,把原函数的 7 个关键属性批量复制到 wrapper 上:
-
__module__、__name__、__qualname__、__doc__、__annotations__直接赋值 -
__dict__合并更新(保留 wrapper 自身属性,同时注入原函数的动态属性) -
__wrapped__指向原始函数,供inspect等工具穿透多层装饰器
它必须作用于最内层的 wrapper 函数,否则复制动作就落空了。比如带参数的装饰器里,@wraps(func) 得写在最终执行逻辑的那个 def wrapper(...) 上,而不是外层闭包函数里。
不加 wraps 会直接影响哪些场景
很多框架和工具依赖函数元数据工作,不是“看起来丑”,而是直接出错:
- FastAPI 和 Pydantic 基于
__annotations__和inspect.signature()做参数校验和序列化,元数据错 → 请求解析失败 - Flask/Django 的路由注册、调试面板、自动文档(如 Swagger UI)靠
__name__和__doc__匹配和展示,错 → 路由不可见或文档空白 - pytest 发现测试函数时按
__name__过滤,错 → 测试被跳过 - IDE 的跳转、补全、hover 提示依赖
__doc__和签名,错 → 开发体验断层
容易踩的坑:wraps 放错位置或漏掉
最常见的实操失误:
- 把
@wraps(func)加在外层装饰器工厂函数上,比如def retry(max_attempts=3): @wraps(...) def decorator(...)—— 这完全无效 - 在
wrapper内部手动调用update_wrapper,但漏掉__annotations__或没处理__dict__合并 - 用类实现装饰器时,忘记在
__call__返回的函数上应用@wraps - 多个装饰器叠加时,只在最外层加
@wraps,而没确保每一层的wrapper都被正确包裹
真正起作用的永远是那一行紧贴 def wrapper(...) 的 @wraps(func) —— 它不是可选优化,是让装饰器能被现代 Python 工具链识别的必要条件。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











