FastAPI 进阶:用 Python dataclasses 声明请求体与响应模型
在 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.py 与 fastapi/_compat/v2.py 中也存在对 is_dataclass 类型的判定逻辑,用于在构建字段信息时正确处理 dataclass 注解。
官方测试的验证
仓库对应的单元测试 tests/test_tutorial/test_dataclasses/test_tutorial001.py 验证了上述行为:
- 合法的请求体
{"name": "Foo", "price": 3}返回200,响应 JSON 会把未提供的description、tax以null补齐; - 非法的请求体(如
price传了字符串"invalid price")返回422,校验错误中"loc": ["body", "price"],错误类型为float_parsing; GET /openapi.json中会生成对Item的 schema 引用{"$ref": "#/components/schemas/Item"},其中name、price为必填,description、tax为可空(anyOf含null)。
可见校验、序列化与文档三件事都真实生效,并非只在概念层面支持。
在 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 时看到的效果一致:
官方测试的验证
tests/test_tutorial/test_dataclasses/test_tutorial002.py 中的断言显示:
GET /items/next返回200,响应体中包含tags: ["breater"],并且未提供的tax自动补成null;- OpenAPI schema 中
Item的tags被描述为{"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",
},
],
},
]
逐点拆解这段代码中隐含的设计决策:
- 仍然从标准库导入
field:即使 dataclass 装饰器换成了 Pydantic 版本,field(default_factory=...)这类标准工具依旧照常用。 pydantic.dataclasses是 drop-in replacement:把from dataclasses import dataclass换成from pydantic.dataclasses import dataclass即可,写法和既有 dataclass 代码保持兼容。Author中嵌套了一组Item:items: list[Item]让 dataclass 之间形成组合关系,字段级校验会沿结构递归进行。Author作为response_model:POST 接口的响应将按Author结构校验与整形。- 请求体也可以是"标准类型注解 + dataclass":
items: list[Item]直接声明请求体是Item列表。 - 返回 dict 也能被序列化:这里返回的是包含
items(元素为 dataclass 的列表)的字典,FastAPI 仍能把数据正确序列化成 JSON。 response_model使用类型注解列表:response_model=list[Author]说明response_model中可以自由组合标准类型注解(如list[...])与 dataclass。- 普通
def也能胜任:这个路径操作函数用的是def而非async def,与 FastAPI 一贯的约定一致——你可以按需混用def与async def。需要回顾"何时用哪种"时,可参考本仓库文档中关于async与await的说明("In a hurry?" 一节)。 - 函数可以不直接返回 dataclass 实例:
get_authors返回的是携带内部数据的字典列表,FastAPI 会借助包含 dataclass 的response_model参数完成响应转换与序列化。
官方测试的验证
tests/test_tutorial/test_dataclasses/test_tutorial003.py 覆盖了上述两个接口的行为:
POST /authors/foo/items/发送两个Item的 JSON 数组,返回200且name回显为路径参数foo,未提供的description自动补为null;GET /authors/返回完整的嵌套列表,所有缺省的description都以null补齐,证明嵌套序列化工作正常;- OpenAPI schema 中
Author的items字段被建模为{"$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 | None、list[Item]),对应测试也通过 needs_py310 标记约束运行环境(参见 tests/utils.py 与 tests/test_tutorial/test_dataclasses/ 下的用例)。若你的 Python 版本低于 3.10,需改用 typing.Optional / typing.List 等旧式注解。
教程原文位于 docs/hi/docs/advanced/dataclasses.md(本仓库的英文原版见 docs/en/docs/advanced/dataclasses.md),可对照查看。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0626
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
