装饰器导致函数元数据丢失是因为返回新函数对象,functools.wraps是必要同步机制,需在每层wrapper上紧贴使用以继承原始函数的__module__、__name__等7个属性。

因为装饰器返回的是新函数对象(通常是闭包里的 wrapper),它和原函数完全无关,所有元数据都来自这个新函数,而不是被装饰的原始函数。
装饰器本质是函数替换,不是功能叠加
写 @my_decorator 等价于执行 func = my_decorator(func)。如果 my_decorator 返回的是一个全新定义的 wrapper 函数,那 func 就彻底指向了它——__name__、__doc__、__annotations__ 这些属性自然也跟着换人。
常见表现包括:
-
help(my_func)显示wrapper(*args, **kwargs)而非原始签名 -
my_func.__name__是"wrapper",不是真实函数名 -
my_func.__doc__为None,即使原函数写了 docstring -
inspect.signature(my_func)在某些装饰器结构下抛出ValueError: Function has no argument info
functools.wraps 不是“可选优化”,而是必要同步机制
functools.wraps 并非简单复制几个字段,它调用 update_wrapper(wrapper, func),默认同步这 7 个关键属性:__module__、__name__、__qualname__、__doc__、__annotations__、__dict__,以及 Python 3.10+ 的 __wrapped__ 引用。
手动赋值容易漏掉 __annotations__ 或动态 __dict__ 属性;而漏掉 __wrapped__ 会让 FastAPI、pytest 等框架无法回溯原始函数。
必须满足两个硬性条件:
-
@wraps(func)必须紧贴在真正执行逻辑的wrapper函数定义上方 - 对带参数的装饰器(如
@retry(max_attempts=3)),@wraps必须放在最内层的wrapper上,不能放在外层工厂函数上
多层装饰时,每一层 wrapper 都要独立加 @wraps
装饰器嵌套(比如 @log_calls → @add_one → square)不是链式传递,而是逐层覆盖。只要某一层没加 @wraps,元数据就在那一层断掉——外层补救无效。
例如:
def log_calls(func):
def wrapper(*args, **kwargs): # ← 没加 @wraps,这里就断了
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
<p>@log_calls
@add_one # 即使 add_one 用了 @wraps,log_calls 已经把 <strong>name</strong> 改成 'wrapper' 了
def square(x): ...
</p>
此时 square.__name__ 仍是 "wrapper",后续所有反射行为都基于错误起点。
真正容易被忽略的是:元数据同步不是“一次设置、永久生效”,而是每层包装函数都必须主动声明自己愿意继承谁的信息。漏掉任意一层,help()、IDE 提示、OpenAPI 文档生成这些依赖自省的环节就会无声失效。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











