根本原因是装饰器用wrapper函数替代原函数导致元数据丢失;functools.wraps自动同步7个关键属性,必须作用于最内层wrapper函数,否则help()和inspect.signature()返回错误信息。

装饰器导致 help() 和 inspect.signature() 失效的根本原因
不加处理的装饰器会用内层包装函数(比如 wrapper)完全替代原函数对象,导致 __name__、__doc__、__annotations__ 等元数据全部变成包装函数自己的信息。调用 help(my_func) 时看到的是 wrapper 的文档,inspect.signature(my_func) 返回的也是 wrapper 的参数签名——这在类型检查、API 文档生成、框架路由解析(如 FastAPI)中会直接出错。
必须用 functools.wraps 而不是手动复制元数据
functools.wraps 不只是复制几个属性,它完整同步了 7 个关键元数据:__module__、__name__、__qualname__、__doc__、__annotations__、__dict__,以及 Python 3.10+ 新增的 __wrapped__ 引用。手动赋值容易漏掉 __annotations__ 或 __dict__,尤其当原函数有动态设置的属性时会失效。
正确写法:
from functools import wraps
<p>def my_decorator(func):
@wraps(func) # ← 这行不能少,且必须紧贴 wrapper 定义
def wrapper(*args, *<em>kwargs):
print("before")
result = func(</em>args, **kwargs)
print("after")
return result
return wrapper
</p>
@wraps(func) 必须作用于最内层函数,且位置不能错
常见错误包括:
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 把
@wraps加在装饰器工厂函数上(即外层函数),而不是wrapper上 - 在
wrapper内部调用wraps,而非作为装饰器使用 - 对带参数的装饰器,忘记把
@wraps放在真正执行逻辑的最内层函数上
带参数装饰器的典型结构:
def retry(max_attempts=3):
def decorator(func):
@wraps(func) # ← 必须在这里,作用于最终的 wrapper
def wrapper(*args, **kwargs):
for i in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception:
if i == max_attempts - 1:
raise
return None
return wrapper
return decorator
即使用了 @wraps,某些场景仍需额外处理
@wraps 解决了元数据问题,但不解决以下情况:
- 原函数被
typing.overload重载时,@wraps不会合并重载签名,需配合typing.overload手动定义 - 使用
__signature__属性硬编码修改过签名的函数,@wraps不会还原该修改,得在wrapper中显式重新赋值 - 装饰器内部改变了参数传递方式(如把
*args展开再传入),inspect.signature返回的仍是原函数签名,但实际调用行为已不同——这时应考虑用inspect.Signature.replace()主动更新
元数据能“修”,但运行时行为和签名语义是否一致,得自己核对。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










