Python functools.wraps 的重要性

不加 @functools.wraps(func) 会导致被装饰函数的 name__、__doc__、__module__、__qualname__、__annotations 等元信息丢失,变为 wrapper 函数的信息,引发调试、文档生成、类型检查及框架反射(如 FastAPI、pytest)异常。

为什么 functools.wraps 不能省略

直接说结论:不加 @functools.wraps(func) 的装饰器,会让被装饰函数的元信息(__name____doc____module__ 等)全部丢失,变成内层包装函数的信息。这在调试、文档生成、类型检查、框架反射(如 FastAPI 路由注册、pytest 参数发现)中会出问题。

functools.wraps 修复哪些具体字段

它本质是调用 update_wrapper,把原函数的关键属性复制给包装函数。默认同步以下字段:

  • __module__:避免显示为 functools 或装饰器所在模块
  • __name__:否则 my_func.__name__ 变成 wrapper
  • __qualname__:影响嵌套函数和类方法的路径表示
  • __doc__:否则 help(my_func) 或 Sphinx 文档抓不到说明
  • __annotations__:类型提示丢失,mypy 和 IDE 自动补全失效

不加 @wraps 的典型报错场景

这些不是语法错误,但会让工具链“认不出”你的函数:

西安网上购物网店系统

西安网上购物网店系统的主要亮点:(1)商品的分类更加细化和明朗,可以三级分类,价格可以多层次\多级别,按照后台设置的,吸引会员加入。(2)会员和非会员购物并存,订单直接支付和会员帐户支付并存,电话支付与网上支付多种支付方式。(3)自定义商品扩展属性,多种扩展属性定义模式,强大的商品管理功能,多重分类功能(4)灵活的会员积分系统,灵活的会员权限控制,模版丰富多彩,模版代码分离,方便修改模版(5)支付

下载

  • FastAPI 报 TypeError: Unable to extract signature for ... —— 因为 inspect.signature() 拿不到原函数签名
  • pytest 发现不了测试函数:若用 @pytest.mark.parametrize 套在没 @wraps 的装饰器上,可能跳过执行
  • help(my_decorated_func) 显示空文档或 Help on function wrapper in module ...
  • 日志里记录函数名变成 wrapper,排查时无法定位真实逻辑

一个最小可验证示例

from functools import wraps

def my_decorator(func): def wrapper(*args, *kwargs): print("before") result = func(args, **kwargs) print("after") return result

忘记加 @wraps(func) → 元信息全丢

return wrapper

@my_decorator
def greet(name: str) -> str:
"""Say hello to someone."""
return f"Hello, {name}!"

print(greet.name) # 输出 'wrapper',不是 'greet'
print(greet.doc) # 输出 None
print(greet.annotations) # 输出 {},不是 {'name': , 'return': }

只需在 def wrapper 后加一行 @wraps(func),所有字段立刻恢复。这个细节小,但一旦漏掉,在协作或集成时容易卡住别人——尤其是当函数要被其他库自动扫描时,它不会报错,只会静默失效。

相关推荐:

如何在Python中为测试用例添加自定义标记_通过pytest.mark进行分类执行

pytest.mark 必须作为装饰器直接作用于测试函数或类,不可写在函数外部;可叠加多个标记,执行时用 -m "mark1 and mark2" 等布尔表达式筛选;未注册标记会触发警告,需在 pytest.ini 或 pyproject.toml 中声明以避免。 pytest.mark 能不能直接用在函数外面 不能。所有 pytest.mark 必须装饰在测试函数或类上,写在函数定义之外会报 S...
· Python · 275301

如何在Python中构建树状结构的递归生成器_使用yield from简化递归代码

yield from 在树遍历中省去了手动展开子生成器的繁琐与风险,自动处理委托调用、异常传播、StopIteration 捕获及协程方法转发,确保扁平化输出和行为一致性。 yield from 在树遍历中到底省了什么 直接说结论:yield from 不是语法糖,它把递归生成器的“委托调用”从手动循环 + yield 拆解成一条语句,避免了中间迭代器被提前耗尽、异常传播断裂、或 StopIter...
· Python · 169187

如何解决Python中的UnboundLocalError局部变量引用错误_使用global或nonlocal关键字声明

Python函数内同名变量报UnboundLocalError,因编译期将赋值变量标记为局部,读取在赋值前则出错;global用于模块级变量,nonlocal用于最近外层函数变量,二者不可混用且需提前声明。 为什么在函数里修改同名变量会报 UnboundLocalError Python 在编译函数时,只要看到某个变量在函数体内有赋值操作(哪怕只在 if 分支里),就会把它标记为局部变量。之后如果...
· Python · 50196

为什么Python脚本内存占用居高不下_使用objgraph与gc模块排查泄露

Python内存泄漏主因是对象被隐性持有而非未调gc.collect(),可用objgraph.show_growth()定位异常增长类型,再用gc.get_referrers()逆向追踪强引用源,并结合RSS监控与gc调试确认真实泄漏。 Python 脚本内存持续上涨,大概率不是“没调 gc.collect()”,而是对象根本没被识别为可回收——比如被全局容器悄悄持有、含 __del__ 的循环...
· Python · 294870

如何在Python Flask中集成Click命令行工具_使用app.cli.command

app.cli.command命令不出现的主因是Flask实例未完成初始化或注册时机过早;需确保在create_app()返回后注册,且装饰器顺序为@app.cli.command在上、@click.option等在下,同时避免顶层导入时执行注册。 为什么 app.cli.command 注册的命令不出现? 最常见原因是 Flask 应用实例未正确初始化 CLI 上下文,或者命令注册时机早于 ap...
· Python · 119961

为什么Python Tkinter Canvas保存的图片是空白_使用ImageGrab截取组件

Canvas无法直接保存为图片,需用ImageGrab截屏并精确计算坐标,Windows高DPI下须启用DPI感知并缩放坐标;更可靠方案是用PIL重绘所有元素。 Canvas 本身不支持直接保存为图片文件,所谓“保存空白”不是 bug,而是你根本没在保存 Canvas 的内容 Canvas 没有内置 save() 或 export() 方法 Tkinter 的 Canvas 是一个绘图容器,它只负...
· Python · 187109

Python Django 5.0如何使用异步视图提高并发_定义async def视图函数

Django 5.0 的 async 视图必须搭配 ASGI 服务器(如 uvicorn)运行,数据库操作需用 sync_to_async 包装或原生异步方法,避免阻塞事件循环;中间件、装饰器也须显式声明为 async 才能协同工作。 async def 视图函数必须搭配 ASGI 服务器运行 Django 5.0 的 async def 视图不会在 WSGI(如 Gunicorn + uWSGI...
· Python · 195451