装饰器返回新函数对象,导致原函数元数据丢失;functools.wraps通过复制__module__、__name__等属性并设置__wrapped__解决该问题。

装饰器返回的是新函数对象,不是原函数
Python中@decorator语法等价于func = decorator(func)。装饰器必须返回一个可调用对象——通常是内部定义的wrapper函数。这个wrapper是全新创建的函数对象,和原函数在内存中完全无关,它的__name__、__doc__等属性自然来自自身定义,而非被包装的函数。
常见表现包括:
-
help(my_func)显示wrapper(*args, **kwargs)而不是真实签名 -
my_func.__name__返回'wrapper',导致日志或测试框架无法识别函数身份 -
my_func.__doc__为None,即使原函数写了完整 docstring
functools.wraps 本质是批量赋值元数据
@wraps(func)不是魔法,它只是调用update_wrapper(wrapper, func),把func的关键属性拷贝到wrapper上。默认复制的字段有:
-
__module__、__name__、__qualname__、__doc__、__annotations__ -
__dict__(通过updated参数,非赋值而是.update())
注意:__defaults__、__kwdefaults__、__closure__这些运行时属性不会被复制——它们不属于“元数据”范畴,inspect.signature()仍能正确解析原函数,因为它读的是func.__code__,不受wraps影响。
带参数的装饰器也必须在最内层 wrapper 上加 @wraps
参数化装饰器有三层嵌套,容易漏掉@wraps的位置。它必须作用于最终返回的那个wrapper函数,而不是中间的decorator闭包。
错误写法:@wraps(func)放在第二层函数上,实际返回的wrapper没被包裹;
正确写法:
from functools import wraps
<p>def retry(max<em>attempts):
def decorator(func):
@wraps(func) # ← 必须在这里,作用于真正的 wrapper
def wrapper(*args, **kwargs):
for </em> in range(max_attempts):
try:
return func(*args, **kwargs)
except:
pass
raise RuntimeError("Failed")
return wrapper
return decorator
</p>
漏掉这行,fetch_data.__name__依然会变成'wrapper',哪怕外层结构再复杂。
__wrapped__ 属性是调试和框架集成的关键线索
functools.wraps还会自动设置wrapper.__wrapped__ = func。这个属性不用于日常开发,但在调试或框架底层(如 FastAPI 的路由注册、pytest 的测试发现)中会被主动读取。如果自定义装饰器没设__wrapped__,某些工具可能抛出ValueError: Function has no argument info之类难以定位的错误。
它不是装饰器的“可选功能”,而是生产环境兼容性的事实标准。没有它,inspect.unwrap()也无法逐层剥开多层装饰,容易卡在某一层wrapper上。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











