不加 @functools.wraps 会丢失原函数的 name__、__doc__、__annotations 等元信息,因装饰器默认返回 wrapper 函数而非原函数,导致 help()、类型检查、框架反射等失效。

为什么 @functools.wraps 不加就丢元信息?
不加 @functools.wraps 时,装饰器返回的是内部包装函数(比如 wrapper),它的 __name__、__doc__、__annotations__ 全都来自这个包装函数,而不是原函数。调试、文档生成(如 help())、类型检查工具(如 mypy)甚至某些框架(如 FastAPI 的路由反射)都会因此失效。
根本原因是:Python 装饰器本质是函数替换,而默认不会自动复制原函数的属性。
@wraps(func) 怎么用才生效?
必须把被装饰的函数名传给 @wraps,且该调用要写在包装函数定义前;否则它不知道该“仿照”谁。
- ✅ 正确:
from functools import wraps <p>def my_decorator(func): @wraps(func) # ← 这里传入 func,不是字符串,也不是 self def wrapper(*args, *<em>kwargs): return func(</em>args, **kwargs) return wrapper
- ❌ 错误:
@wraps(wrapper)、@wraps('func')、漏掉@wraps行、或把它放在def wrapper之后 - ⚠️ 注意:
@wraps只复制__module__、__name__、__qualname__、__doc__、__annotations__这几个关键属性,不复制__dict__或自定义属性
不加 @wraps 会触发哪些具体问题?
常见报错或异常行为往往不直接提示“缺 wraps”,得靠现象反推:
仅使用 Python 将 Claw Code 虚拟化框架与 OpenClaw 集成,提供 184 种工具和 200 多条命令,用于安全分析和代码质量...
立即学习“Python免费学习笔记(深入)”;
-
help(my_decorated_func)显示的是wrapper的 docstring,甚至为空 - 单元测试里用
assert my_func.__name__ == 'my_func'失败,实际值是'wrapper' - 使用
inspect.signature(my_decorated_func)拿到的是包装函数的签名,而非原函数参数(除非你在wrapper里手动处理*args, **kwargs并调用inspect.signature(func)) - FastAPI 或 Flask 中,带类型注解的路由函数若没
@wraps,依赖注入可能失败或参数解析错乱
替代方案和边界情况
@wraps 是标准解法,但有几点容易忽略:
- 如果装饰器本身带参数(即三层嵌套),
@wraps仍要作用在最内层的wrapper上,且传入原始func(不是中间闭包里的变量名) -
functools.wraps本身返回一个装饰器函数,所以它必须被调用(带括号),写成@wraps等价于@wraps(func)—— 少了括号就是语法错误 - 极少数场景需要额外复制属性(比如
__version__),就得手动赋值:wrapper.__version__ = func.__version__
真正麻烦的不是怎么写,而是忘了写,或者写在了错误的位置——一旦出问题,往往要倒查好几层装饰器链。

















