FastAPI 进阶实战:用 Dataclasses 声明请求体、响应模型与嵌套数据结构
本文基于 FastAPI 官方文档《Using Dataclasses》展开,讲清楚 FastAPI 对标准库 dataclasses 的原生支持机制:你可以把已有的标准 @dataclass 类直接当作请求体、response_model 和嵌套数据结构来用,而底层由 Pydantic 完成验证、序列化与 OpenAPI 文档生成。读完本篇,你将掌握标准 dataclass 与 pydantic.dataclasses 两种写法在 FastAPI 中的适用场景,并能从源码层面理解 FastAPI 是如何识别并转换这些 dataclass 的。
为什么 FastAPI 能直接使用标准库 dataclasses
FastAPI 构建在 Pydantic 之上,官方教程中一直演示用 Pydantic 模型来声明请求和响应。但 Pydantic 本身内置了对标准库 dataclasses 的支持,因此 FastAPI 也支持同样的用法:即使你的代码没有显式引入 Pydantic,FastAPI 也会借助 Pydantic 把你定义的标准 dataclass 转换成 Pydantic 自己的 dataclass 形式,并由此获得与 Pydantic 模型完全一致的能力:
- 数据验证(data validation)
- 数据序列化(data serialization)
- 数据文档化(data documentation),例如自动生成 OpenAPI Schema
需要注意的是:dataclasses 不能做 Pydantic 模型能做的所有事情,某些复杂场景下你可能仍需回到 Pydantic 模型。但如果你手头已经有一批现成的标准库 dataclass,用它们直接驱动一个 FastAPI Web API 是一个非常实用的技巧。
这个特性自 FastAPI 版本 0.67.0 起可用。
用标准库 dataclass 作为请求体
最直接的用法是:定义一个标准 @dataclass,然后把它作为路径操作函数的参数注解。下面代码来自 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
要点解析:
Item是一个标准的dataclasses.dataclass,未引入任何 Pydantic 组件。description与tax声明为str | None = None/float | None = None,即可选字段,默认值为None;而name与price没有默认值,因此是必填字段。- 路径操作函数
create_item直接以item: Item声明请求体。FastAPI 会自动解析 JSON 请求体、验证字段、构造Item实例并返回。
仓库中的自动化测试 tests/test_tutorial/test_dataclasses/test_tutorial001.py 验证了这一行为:POST /items/ 提交 {"name": "Foo", "price": 3} 后返回 200,响应 JSON 包含补齐默认值后的完整结构(description: None、tax: None),证明验证与默认值填充确实生效。
源码层面的原理:FastAPI 在判断一个字段注解是“标量”还是“复杂结构”时,会显式检查 is_dataclass。见 fastapi/_compat/shared.py:
def _annotation_is_complex(annotation: type[Any] | None) -> bool:
return (
lenient_issubclass(annotation, (BaseModel, Mapping, UploadFile))
or _annotation_is_sequence(annotation)
or is_dataclass(annotation)
)
正是这个 is_dataclass(annotation) 分支,使得标准 dataclass 注解被归类为“复杂”结构,从而被当作请求体(body)字段处理,而不是误判为单个 query/path 参数。随后 Pydantic 在验证阶段将其转换为自己的 dataclass 表示。
在 response_model 中使用 dataclasses
dataclass 同样可以传入 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"],
}
这里有几个值得注意的细节:
- 使用了标准库的
field(default_factory=list)为列表字段设置默认值——这是标准 dataclass 的惯用写法,FastAPI 完全兼容。 response_model=Item中的Item是一个标准 dataclass,它会被自动转换为 Pydantic dataclass。- 由于转换后的 dataclass 拥有完整的字段 Schema,它会出现在 API 文档用户界面(Swagger UI / ReDoc)中,作为
response_model的响应 Schema 展示。 - 路径操作函数返回的是普通字典而非 dataclass 实例,FastAPI 依然能按
Item的结构完成过滤、验证与 JSON 序列化(注意tax字段因未在字典中提供而取默认值None)。
对应的测试 tests/test_tutorial/test_dataclasses/test_tutorial002.py 断言 GET /items/next 返回 200 且 JSON 结构符合 Item 的完整字段。
更广泛的序列化行为在 tests/test_serialize_response_dataclass.py 中有系统覆盖,包括:返回 dataclass 实例、返回字典、返回需要类型强制转换的值(如 ISO 格式日期字符串、字符串形式的浮点数)、response_model=list[Item] 的列表场景,以及不设置 response_model 时直接返回 dataclass 实例的场景——所有情况下 datetime 都会被序列化为 ISO 字符串,可选字段缺省输出 null。
源码层面:response_model 的字段收集逻辑同样识别 dataclass。见 fastapi/_compat/v2.py 的 get_model_fields:
def get_model_fields(model: type[BaseModel]) -> list[ModelField]:
model_fields: list[ModelField] = []
for name, field_info in model.model_fields.items():
type_ = field_info.annotation
if lenient_issubclass(type_, (BaseModel, dict)) or is_dataclass(type_):
model_config = None
else:
model_config = model.model_config
...
当外层模型中嵌套了 dataclass 类型字段时,会按独立模型处理而不继承外层 config,保证嵌套 dataclass 按自身规则生成 Schema。此外,即便完全没有走 response_model,FastAPI 的 JSON 编码器也内置了 dataclass 分支,见 fastapi/encoders.py:
if dataclasses.is_dataclass(obj):
assert not isinstance(obj, type)
obj_dict = dataclasses.asdict(obj)
return jsonable_encoder(
obj_dict,
include=include,
exclude=exclude,
...
)
jsonable_encoder 通过 dataclasses.asdict() 把 dataclass 实例递归展开为字典,再逐项编码为可 JSON 化的结构,并支持 include/exclude/exclude_none 等过滤参数。
嵌套数据结构与 pydantic.dataclasses 的替换
你还可以把 dataclasses 与其他标准类型注解组合,构造嵌套数据结构。
某些情况下(例如自动生成的 API 文档出错时),你可能需要使用 Pydantic 版本的 dataclasses。此时只需把标准 dataclasses 换成 pydantic.dataclasses——它是**可直接替换(drop-in replacement)**的,而 field 仍然从标准库 dataclasses 导入即可。
下面代码来自 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仍然从标准dataclasses导入。pydantic.dataclasses是dataclasses的直接替换(drop-in replacement),只需修改导入语句。Authordataclass 内嵌了一个list[Item]字段——即 dataclass 中包含 dataclass 列表,构造嵌套结构。Authordataclass 被用作response_model参数。- 请求体使用了标准类型注解
list[Item]——dataclass 可以直接和list[...]等通用类型组合作为请求体。 - 路径操作函数返回一个包含
items(dataclass 列表)的字典。FastAPI 依然能把数据序列化为 JSON。 - 此处的
response_model使用list[Author]类型注解——再次体现 dataclass 与标准类型注解的自由组合。 - 注意该路径操作函数用的是普通
def而非async def。在 FastAPI 中def与async def可按需混用;如果对何时使用哪个拿不准,可以回顾官方文档中关于async与await的“In a hurry?”章节(见 docs/en/docs/async.md)。 - 该函数并没有返回 dataclass(虽然也可以返回),而是返回了包含内部数据的字典列表。FastAPI 会使用
response_model(其中包含 dataclass)参数来转换响应。
你可以把 dataclasses 与其他类型注解以多种不同组合方式搭配,构造出复杂的嵌套数据结构;上面的代码内注释给出了具体的细节指引。
对应的验证测试位于 tests/test_tutorial/test_dataclasses/test_tutorial003.py,覆盖了两个端点的请求验证、路径参数注入与嵌套序列化行为。
dataclasses 与 Pydantic 模型的混用与局限
dataclasses 与 Pydantic 模型之间没有墙:你可以把 dataclasses 与其他 Pydantic 模型组合使用、从 Pydantic 模型继承、把自己的 dataclass 包含进 Pydantic 模型中,等等。深入用法可以查阅 Pydantic 官方文档中关于 dataclasses 的章节(Pydantic 支持把标准库 dataclass 直接用于 BaseModel 等场景,这也是 FastAPI 支持能力的源头)。
再强调一遍文档中的关键提醒:dataclasses 不能做 Pydantic 模型能做的所有事情。如果你的需求超出标准 dataclass 的能力范围(例如复杂的字段级验证器、动态配置模型行为等),仍然需要使用 Pydantic 模型。但如果项目中已经存在一批标准 dataclass,直接用它们驱动 FastAPI API 是一条非常省事的路径。
版本要求与适用前提
- 该特性自 FastAPI
0.67.0版本起可用,当前仓库源码基于 Pydantic v2 的兼容层实现,因此标准 dataclass 与pydantic.dataclasses两种写法均受支持。 - 文档示例代码使用
str | None、list[str]等 Python 3.10+ 语法(源码文件名中的_py310后缀即表明最低 Python 版本为 3.10);测试中通过 tests/utils.py 的needs_py310标记控制运行条件。如果你的 Python 版本较低,可改用Optional[str]、List[str]等价写法。 - 组合 dataclass 与 Pydantic 模型时,行为以 Pydantic 对 dataclass 的支持为准;遇到自动生成的 API 文档异常时,优先尝试切换为
pydantic.dataclasses。
关键文件索引
| 内容 | 仓库相对路径 |
|---|---|
| 本文对应的官方文档 | docs/en/docs/advanced/dataclasses.md |
| 请求体示例(标准 dataclass) | docs_src/dataclasses_/tutorial001_py310.py |
response_model 示例 |
docs_src/dataclasses_/tutorial002_py310.py |
嵌套结构与 pydantic.dataclasses 示例 |
docs_src/dataclasses_/tutorial003_py310.py |
复杂注解识别(is_dataclass 分支) |
fastapi/_compat/shared.py |
| 字段收集中的 dataclass 处理 | fastapi/_compat/v2.py |
| JSON 编码器的 dataclass 分支 | fastapi/encoders.py |
| 教程自动化测试 | tests/test_tutorial/test_dataclasses/ |
| dataclass 响应序列化测试 | tests/test_serialize_response_dataclass.py |
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 StartedRust0627
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
