首页
/ FastAPI 进阶:用 Python dataclasses 声明请求体与响应模型

FastAPI 进阶:用 Python dataclasses 声明请求体与响应模型

2026-09-07 14:12:11作者:袁立春Spencer

在 FastAPI 中,除了 Pydantic models,你还可以使用 Python 标准库自带、无需额外引入 Pydantic 语法就能书写的 dataclasses 来声明请求体和响应模型。本文以 FastAPI 官方高级教程为骨架,基于本仓库中的示例源码(docs_src/dataclasses_/ 目录)与对应测试(tests/test_tutorial/test_dataclasses/ 目录),系统讲解 dataclasses 的基础用法、在 response_model 中的使用、以及如何在嵌套数据结构中与标准类型注解自由组合。读完后你将掌握:用最小改动把已有 dataclasses 直接变成 Web API 的请求/响应契约,并理解其背后由 Pydantic 驱动的数据校验、序列化与文档生成机制。


为什么 FastAPI 支持 dataclasses

FastAPI 构建在 Pydantic 之上,此前教程中一直用 Pydantic models 声明请求(request)和响应(response)。FastAPI 同样支持以相同方式使用 Python 标准库的 dataclasses。以本仓库示例文件 docs_src/dataclasses_/tutorial001_py310.py 为例:

from dataclasses import dataclass

from fastapi import FastAPI


@dataclass
class Item:
    name: str
    price: float
    description: str | None = None
    tax: float | None = None


app = FastAPI()


@app.post("/items/")
async def create_item(item: Item):
    return item

代码中完全没有显式出现 Pydantic 的 BaseModel,只是定义了一个普通的 Python dataclass Item,随后直接把它用作路径操作函数 create_item 的参数类型。运行后:

  • POST /items/ 收到的 JSON 请求体会被按 Item 的字段结构自动校验和解析;
  • 返回的 item 对象会被序列化为 JSON 响应。

这一切之所以成立,是因为 Pydantic 内部本身就内置了对 dataclasses 的支持。FastAPI 在接收到这个标准 dataclass 后,会借助 Pydantic 把它转换成 Pydantic 自己的 dataclass 形态再走统一的校验/序列化管线。也就是说,即使代码表面没有使用 Pydantic,底层依然是 Pydantic 在工作。

dataclasses 同样获得的能力

得益于上述机制,dataclasses 与 Pydantic models 一样支持:

  • 数据校验(data validation):错误输入会得到 422 校验错误响应;
  • 数据序列化(data serialization):能自动把对象转成可传输的 JSON;
  • 数据文档(data documentation):字段结构会出现在 OpenAPI schema 与交互式 API 文档中。

源码层面的印证

在 FastAPI 源码中可以看到 dataclass 处理路径的存在。例如 fastapi/encoders.py 中,jsonable_encoder 专门处理了 dataclass 类型的对象:

if dataclasses.is_dataclass(obj):
    assert not isinstance(obj, type)
    obj_dict = dataclasses.asdict(obj)
    return jsonable_encoder(
        obj_dict,
        include=include,
        exclude=exclude,
        by_alias=by_alias,
        ...
    )

即当对象是 dataclass 实例时,先通过 dataclasses.asdict 转成字典,再继续走统一的 JSON 编码流程。这正是教程中 dataclass 能直接返回给客户端、并被正确序列化的底层依据。此外,fastapi/_compat/shared.pyfastapi/_compat/v2.py 中也存在对 is_dataclass 类型的判定逻辑,用于在构建字段信息时正确处理 dataclass 注解。

官方测试的验证

仓库对应的单元测试 tests/test_tutorial/test_dataclasses/test_tutorial001.py 验证了上述行为:

  • 合法的请求体 {"name": "Foo", "price": 3} 返回 200,响应 JSON 会把未提供的 descriptiontaxnull 补齐;
  • 非法的请求体(如 price 传了字符串 "invalid price")返回 422,校验错误中 "loc": ["body", "price"],错误类型为 float_parsing
  • GET /openapi.json 中会生成对 Item 的 schema 引用 {"$ref": "#/components/schemas/Item"},其中 nameprice 为必填,descriptiontax 为可空(anyOfnull)。

可见校验、序列化与文档三件事都真实生效,并非只在概念层面支持。


response_model 中使用 dataclasses

dataclasses 不仅能作请求体,还可以用作 response_model 参数。示例见 docs_src/dataclasses_/tutorial002_py310.py

from dataclasses import dataclass, field

from fastapi import FastAPI


@dataclass
class Item:
    name: str
    price: float
    tags: list[str] = field(default_factory=list)
    description: str | None = None
    tax: float | None = None


app = FastAPI()


@app.get("/items/next", response_model=Item)
async def read_next_item():
    return {
        "name": "Island In The Moon",
        "price": 12.99,
        "description": "A place to be playin' and havin' fun",
        "tags": ["breater"],
    }

要点说明:

  • Item 中可变默认值 tags 使用了 field(default_factory=list),这是 Python dataclass 的正确写法(避免直接使用可变默认值);
  • 路径操作函数返回的是一个普通 dict,而不是 Item 实例;
  • 由于声明了 response_model=Item,FastAPI 会把 dataclass 自动转换成 Pydantic dataclass,再据此校验并整形响应数据,最后序列化返回。

响应字段结构自动进入 API 文档

一旦 dataclass 被用作 response_model,其字段 schema 就会出现在 API 文档的交互界面中(Swagger UI 的 Response 部分),与使用 Pydantic models 时看到的效果一致:

FastAPI 文档界面中 dataclass 响应模型的字段 schema

官方测试的验证

tests/test_tutorial/test_dataclasses/test_tutorial002.py 中的断言显示:

  • GET /items/next 返回 200,响应体中包含 tags: ["breater"],并且未提供的 tax 自动补成 null
  • OpenAPI schema 中 Itemtags 被描述为 {"type": "array", "items": {"type": "string"}},说明可变列表字段也被完整地记录进了 API 文档。

也就是说,dataclass 只要出现在 response_model 位置,就能获得响应过滤、空值处理与 schema 生成这些与 Pydantic models 等价的能力。


在嵌套数据结构中使用 dataclasses

dataclasses 还可以与其它类型注解组合,构建任意深度的嵌套数据结构,例如"作者包含多个条目"这种一对多模型。此时有些场景(比如自动生成的 API 文档出现错误)可能需要使用 Pydantic 提供的 dataclass 版本,方法是把标准库的 dataclasses 直接替换为 pydantic.dataclasses——它是一个 drop-in replacement(即插即用替代品),导入名不变、使用方式不变。

完整示例见 docs_src/dataclasses_/tutorial003_py310.py

from dataclasses import field  # (1)

from fastapi import FastAPI
from pydantic.dataclasses import dataclass  # (2)


@dataclass
class Item:
    name: str
    description: str | None = None


@dataclass
class Author:
    name: str
    items: list[Item] = field(default_factory=list)  # (3)


app = FastAPI()


@app.post("/authors/{author_id}/items/", response_model=Author)  # (4)
async def create_author_items(author_id: str, items: list[Item]):  # (5)
    return {"name": author_id, "items": items}  # (6)


@app.get("/authors/", response_model=list[Author])  # (7)
def get_authors():  # (8)
    return [  # (9)
        {
            "name": "Breaters",
            "items": [
                {
                    "name": "Island In The Moon",
                    "description": "A place to be playin' and havin' fun",
                },
                {"name": "Holy Buddies"},
            ],
        },
        {
            "name": "System of an Up",
            "items": [
                {
                    "name": "Salt",
                    "description": "The kombucha mushroom people's favorite",
                },
                {"name": "Pad Thai"},
                {
                    "name": "Lonely Night",
                    "description": "The mostests lonliest nightiest of allest",
                },
            ],
        },
    ]

逐点拆解这段代码中隐含的设计决策:

  1. 仍然从标准库导入 field:即使 dataclass 装饰器换成了 Pydantic 版本,field(default_factory=...) 这类标准工具依旧照常用。
  2. pydantic.dataclasses 是 drop-in replacement:把 from dataclasses import dataclass 换成 from pydantic.dataclasses import dataclass 即可,写法和既有 dataclass 代码保持兼容。
  3. Author 中嵌套了一组 Itemitems: list[Item] 让 dataclass 之间形成组合关系,字段级校验会沿结构递归进行。
  4. Author 作为 response_model:POST 接口的响应将按 Author 结构校验与整形。
  5. 请求体也可以是"标准类型注解 + dataclass"items: list[Item] 直接声明请求体是 Item 列表。
  6. 返回 dict 也能被序列化:这里返回的是包含 items(元素为 dataclass 的列表)的字典,FastAPI 仍能把数据正确序列化成 JSON。
  7. response_model 使用类型注解列表response_model=list[Author] 说明 response_model 中可以自由组合标准类型注解(如 list[...])与 dataclass。
  8. 普通 def 也能胜任:这个路径操作函数用的是 def 而非 async def,与 FastAPI 一贯的约定一致——你可以按需混用 defasync def。需要回顾"何时用哪种"时,可参考本仓库文档中关于 asyncawait 的说明("In a hurry?" 一节)
  9. 函数可以不直接返回 dataclass 实例get_authors 返回的是携带内部数据的字典列表,FastAPI 会借助包含 dataclass 的 response_model 参数完成响应转换与序列化。

官方测试的验证

tests/test_tutorial/test_dataclasses/test_tutorial003.py 覆盖了上述两个接口的行为:

  • POST /authors/foo/items/ 发送两个 Item 的 JSON 数组,返回 200name 回显为路径参数 foo,未提供的 description 自动补为 null
  • GET /authors/ 返回完整的嵌套列表,所有缺省的 description 都以 null 补齐,证明嵌套序列化工作正常;
  • OpenAPI schema 中 Authoritems 字段被建模为 {"$ref": "#/components/schemas/Item"} 数组,两个接口共享同一份 Item 组件定义,没有产生重复 schema。

这组测试同时证明了:路径参数(author_id)、数组形式的请求体、嵌套 dataclass 的响应模型三者可以协同工作。


使用边界:dataclasses 不能替代 Pydantic models

需要特别说明:dataclasses 并不能做到 Pydantic models 能做的一切。因此某些高级场景(如自定义验证器、复杂的模型配置、schema 定制等)仍要回归 Pydantic models。

不过,如果你的项目中已经沉淀了大量现成的 dataclass 定义,这正是一个"零成本复用"的技巧:不需要逐个改写为 BaseModel,直接用它们即可驱动一个 Web API。FastAPI 在背后完成类型转换、校验、序列化和文档生成,代码改动量极小。

进一步地,dataclasses 还可以与其它 Pydantic models 互相组合、继承,或作为字段嵌入到自己的模型中。更深入的用法可参阅 Pydantic 官方文档中关于 dataclasses 的专题章节。


版本与示例代码说明

本功能自 FastAPI 版本 0.67.0 起可用。仓库中的配套示例均位于 docs_src/dataclasses_/ 目录,文件名带 _py310 后缀,表示示例使用了 Python 3.10+ 的联合类型写法(如 str | Nonelist[Item]),对应测试也通过 needs_py310 标记约束运行环境(参见 tests/utils.pytests/test_tutorial/test_dataclasses/ 下的用例)。若你的 Python 版本低于 3.10,需改用 typing.Optional / typing.List 等旧式注解。

教程原文位于 docs/hi/docs/advanced/dataclasses.md(本仓库的英文原版见 docs/en/docs/advanced/dataclasses.md),可对照查看。

登录后查看全文
热门项目推荐
相关项目推荐