news 2026/9/8 20:24:54

FastAPI Response Model 响应模型全解析:用返回类型与 `response_model` 精准定义、校验并过滤接口输出

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FastAPI Response Model 响应模型全解析:用返回类型与 `response_model` 精准定义、校验并过滤接口输出

FastAPI Response Model 响应模型全解析:用返回类型与response_model精准定义、校验并过滤接口输出

【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi

本篇文章基于 FastAPI 官方教程(Response Model - Return Type,源码示例位于 docs_src/response_model)展开。它系统讲解如何通过path operation function 的返回类型注解装饰器参数response_model来声明响应结构,以及 FastAPI 如何基于此完成响应数据校验、OpenAPI JSON Schema 生成、JSON 序列化与最关键的输出字段过滤。读完本文,你将掌握在真实业务中为每个接口"削平"多余数据(尤其防止密码等隐私字段外泄)的完整方案,并理解返回类型、response_model与各类响应编码参数(response_model_exclude_unsetresponse_model_include等)的取舍与底层原理。

一、返回类型即响应契约:用 Return Type 声明响应结构

FastAPI 允许你像给函数参数声明输入类型那样,用path operation function返回类型(return type)来声明响应的数据类型。返回类型可以是 Pydantic model、listdict、整数/布尔等标量值,也可以是它们的任意合法组合。

最基础的写法如下(完整示例见 tutorial001_01_py310.py):

from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class Item(BaseModel): name: str description: str | None = None price: float tax: float | None = None tags: list[str] = [] @app.post("/items/") async def create_item(item: Item) -> Item: return item @app.get("/items/") async def read_items() -> list[Item]: return [ Item(name="Portal Gun", price=42.0), Item(name="Plumbus", price=32.0), ]

其中第 16 行-> Item与第 21 行-> list[Item]就是响应声明。FastAPI 会把这个返回类型用于四件事:

  1. 校验(Validate)返回数据:如果函数实际返回的数据不合法(例如缺少某个必填字段),说明你的应用代码出了问题——它没有返回它本该返回的东西。此时 FastAPI 会返回一个server error,而不是带着错误数据继续响应,从而让你和客户端都能确信收到的数据与数据结构是符合预期的。
  2. 为 OpenAPI path operation 生成响应的 JSON Schema:该 Schema 会被自动交互文档/docs)使用,也会被自动化的客户端代码生成工具使用。
  3. 用 Pydantic 将返回数据序列化为 JSON:Pydantic 的核心序列化层由 Rust 编写,因此这一过程"非常快"。
  4. 最关键的是:把输出数据限制并过滤到返回类型所定义的范围内。函数哪怕多返回了字段,最终响应里也只保留类型中声明过的字段。这一点对安全尤其重要,下文会反复看到它的价值。

二、response_model参数:当"实际返回"与"类型声明"不一致时

2.1 为什么需要它

有些场景下,你实际返回的数据与类型注解所声明的并不完全吻合。例如你希望返回一个dict或数据库对象,但想让 FastAPI 以某个 Pydantic model 的视角去完成数据文档化、校验等全部工作。

此时如果直接写返回类型注解,编辑器和静态检查工具会(正确地)报错:你的函数返回了dict,却声明返回 Pydantic model。解决办法是改用path operation decorator参数response_model

2.2 用法与作用范围

response_model可以用在任意 path operation 上:@app.get()@app.post()@app.put()@app.delete()等。示例见 tutorial001_py310.py:

from typing import Any from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class Item(BaseModel): name: str description: str | None = None price: float tax: float | None = None tags: list[str] = [] @app.post("/items/", response_model=Item) async def create_item(item: Item) -> Any: return item @app.get("/items/", response_model=list[Item]) async def read_items() -> Any: return [ {"name": "Portal Gun", "price": 42.0}, {"name": "Plumbus", "price": 32.0}, ]

这里函数实际返回的是裸dictdict列表,但通过response_model声明了输出契约。需要注意:

  • response_model"装饰器"方法(getpost等)的参数,不是path operation function自身的参数(函数参数是路径参数、查询参数和请求体那一套)。
  • response_model接收的类型与你在 Pydantic model 字段里声明的类型相同,所以它可以是一个 Pydantic model,也可以是如List[Item]这样的 Pydantic model 列表。
  • FastAPI 会用该response_model完成数据文档化、校验等,并把输出数据转换并过滤到其类型声明的范围。

如果编辑器 / mypy 开启了严格类型检查,你可以把函数返回类型声明为Any,明确告诉编辑器"我是有意返回任意内容的";FastAPI 依然会依据response_model做文档化、校验与过滤。

2.3response_model的优先级

同时声明了返回类型与response_modelresponse_model优先,FastAPI 会使用它。

这带来一个很好的实践:即使实际返回类型与响应模型不同,你也尽可以在函数上写正确的类型注解供编辑器与 mypy 使用,同时仍让 FastAPI 通过response_model完成数据校验与文档化。

另外,可用response_model=None关闭该 path operation 的响应模型生成。当你只是为某些并非合法 Pydantic 字段的东西添加类型注解时(后文会有示例),就必须这样做。

从实现上看,这些参数最终都会落入路由的序列化流程。在 fastapi/routing.py 中,APIRoute保存了response_fieldresponse_model_includeresponse_model_excluderesponse_model_by_aliasresponse_model_exclude_unsetresponse_model_exclude_defaultsresponse_model_exclude_none等成员;序列化响应时(参见 fastapi/routing.py 与 fastapi/routing.py),会把includeexcludeby_aliasexclude_unsetexclude_defaultsexclude_none一起交给 Pydantic 完成数据过滤与输出。

三、经典陷阱:把输入模型直接当输出模型,明文密码回显

下面定义一个含明文密码的输入模型UserIn(示例见 tutorial002_py310.py):

from fastapi import FastAPI from pydantic import BaseModel, EmailStr app = FastAPI() class UserIn(BaseModel): username: str password: str email: EmailStr full_name: str | None = None # Don't do this in production! @app.post("/user/") async def create_user(user: UserIn) -> UserIn: return user

使用EmailStr前需要先安装校验器依赖email-validator。可用以下任一命令加入项目:

$ uv add email-validator

或用:

$ uv add "pydantic[email]"

本例用同一个UserIn既声明输入又声明输出。当浏览器带着密码创建用户时,API 会在响应里原样返回这个密码。对创建者本人也许不算泄露,可一旦把同一个模型复用到其它 path operation,就可能把用户的密码发给每一个客户端。

⚠️危险:除非你完全清楚其中的各种陷阱并且知道自己正在做什么,否则永远不要以这种方式存储或返回用户的明文密码。

四、正确做法:分离输入/输出模型,由 FastAPI 过滤隐私字段

更稳妥的方案是建两个模型:带明文密码的输入模型UserIn,与不含密码的输出模型UserOut。完整代码见 tutorial003_py310.py:

from typing import Any from fastapi import FastAPI from pydantic import BaseModel, EmailStr app = FastAPI() class UserIn(BaseModel): username: str password: str email: EmailStr full_name: str | None = None class UserOut(BaseModel): username: str email: EmailStr full_name: str | None = None @app.post("/user/", response_model=UserOut) async def create_user(user: UserIn) -> Any: return user

这里虽然path operation function返回的仍是含密码的输入用户对象,但response_model=UserOut声明了不含password的输出契约。因此FastAPI 会负责(借助 Pydantic)把输出模型未声明的所有数据过滤掉,客户端永远拿不到密码字段。

4.1response_model还是返回类型?

本案例中UserInUserOut是两个不同的类。若把函数返回类型注解成UserOut,编辑器与工具会立刻报"返回了无效类型"——因为函数实际返回的是另一个类的实例。这正是本例必须使用response_model参数的原因。

五、返回类型与数据过滤:用类继承兼得"工具支持"与"字段裁剪"

上一节为了过滤数据不得不放弃返回类型带来的工具支持。但在绝大多数"只想从返回结果里裁掉一些字段"的场景中,可以借助类的继承同时获得两者。

先看官方给出的升级版写法(完整代码见 tutorial003_01_py310.py):

from fastapi import FastAPI from pydantic import BaseModel, EmailStr app = FastAPI() class BaseUser(BaseModel): username: str email: EmailStr full_name: str | None = None class UserIn(BaseUser): password: str @app.post("/user/") async def create_user(user: UserIn) -> BaseUser: return user
  • BaseUser持有基础字段;
  • UserIn(BaseUser)继承BaseUser并追加password字段,因而包含全部字段;
  • 函数返回类型注解为BaseUser,实际返回的却是UserIn实例。

这样写之后:编辑器、mypy 等工具不抱怨(UserInBaseUser的子类,类型上合法),FastAPI 又依据BaseUser对输出做了过滤。这是怎么做到的?

5.1 工具视角:类型注解为什么合法

从类型系统看,UserInBaseUser的子类,凡期望"任意BaseUser"的位置,UserIn都是合法类型,所以编辑器与 mypy 不会报错,代码补全与类型检查能力得以保留。

5.2 FastAPI 视角:过滤时不采用继承规则

对于 FastAPI,它会读取返回类型,并确保你返回的数据只包含类型里声明的字段。关键点在于:FastAPI 在内部借助 Pydantic 做了若干处理,避免"类的继承规则"被套用到返回数据的过滤上——否则子类新增的字段会随继承一起被返回,最终吐出的数据将远超预期。

这一行为的正确性在仓库测试中得到专门验证:例如 tests/test_response_model_data_filter.py 与 tests/test_response_model_data_filter_no_inheritance.py 覆盖了"返回类型为父类、实际返回子类/含额外字段对象"时字段应被裁剪的语义。这样你就能同时拿到两样好处:有工具支持的返回类型注解+FastAPI 的数据过滤

六、在自动文档中验证效果

打开自动交互文档,可以确认输入模型与输出模型各自拥有独立的 JSON Schema

两个模型也分别被用于交互式 API 文档:请求体(Request body)按含passwordUserIn渲染,200 响应示例则按不含passwordUserOut渲染:

七、其它返回类型注解:直接返回 Response 的场景

有时你会返回一些并非合法 Pydantic 字段的对象,却仍想在函数上写返回类型注解,目的只是获取编辑器与 mypy 的工具支持。下面是几类典型情况。

7.1 直接返回Response

最常见的场景是像高级教程里讲的那样直接返回一个Response。示例见 tutorial003_02_py310.py:

from fastapi import FastAPI, Response from fastapi.responses import JSONResponse, RedirectResponse app = FastAPI() @app.get("/portal") async def get_portal(teleport: bool = False) -> Response: if teleport: return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ") return JSONResponse(content={"message": "Here's your interdimensional portal."})

当返回类型注解是Response类(或其子类)时,FastAPI 会自动处理这个简单情况,不会尝试把它当作 Pydantic model。同时工具也满意——RedirectResponseJSONResponse都是Response的子类,注解类型是准确的。

7.2 注解一个Response子类

你还可以在注解中直接使用Response的子类。见 tutorial003_03_py310.py:

from fastapi import FastAPI from fastapi.responses import RedirectResponse app = FastAPI() @app.get("/teleport") async def get_teleport() -> RedirectResponse: return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")

这同样成立:RedirectResponseResponse的子类,FastAPI 会自动处理该简单情况。

7.3 非法的返回类型注解(会报错 💥)

但若返回的是数据库对象等任意非 Pydantic 类型对象,并把它写成返回类型注解,FastAPI 会尝试从这个注解创建 Pydantic response model 并失败

同理,若在多种类型之间使用union(联合类型,"这些类型中的任意一个"),而其中一种或多种并非合法 Pydantic 类型,也会失败。例如 tutorial003_04_py310.py:

from fastapi import FastAPI, Response from fastapi.responses import RedirectResponse app = FastAPI() @app.get("/portal") async def get_portal(teleport: bool = False) -> Response | dict: if teleport: return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ") return {"message": "Here's your interdimensional portal."}

它失败的原因在于:该类型注解既不是合法 Pydantic 类型,也不是单一的Response类或其子类,而是Responsedict的联合(二选一)。

7.4 关闭响应模型:response_model=None

承接上面的例子:你可能既不想要 FastAPI 默认的数据校验、文档化与过滤,又想在函数上保留返回类型注解以获取编辑器与类型检查器(如 mypy)的支持。此时设置response_model=None即可。见 tutorial003_05_py310.py:

from fastapi import FastAPI, Response from fastapi.responses import RedirectResponse app = FastAPI() @app.get("/portal", response_model=None) async def get_portal(teleport: bool = False) -> Response | dict: if teleport: return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ") return {"message": "Here's your interdimensional portal."}

response_model=None会让 FastAPI 跳过该 path operation 的响应模型生成,于是你可以随意书写需要的返回类型注解,而不会影响 FastAPI 应用本身。

八、响应编码参数:控制默认值是否进入响应

先看一个带默认值的响应模型(完整代码见 tutorial004_py310.py):

from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class Item(BaseModel): name: str description: str | None = None price: float tax: float = 10.5 tags: list[str] = [] items = { "foo": {"name": "Foo", "price": 50.2}, "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2}, "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []}, } @app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True) async def read_item(item_id: str): return items[item_id]

其中的默认值包括:

  • description: str | None = None(Python 3.10 中Union[str, None] = None的简写):默认为None
  • tax: float = 10.5:默认为10.5
  • tags: list[str] = []:默认为空列表[]

如果这些值在数据源中其实并未被存储,你可能不希望它们混进响应。典型的例子是 NoSQL 数据库里带大量可选属性的模型——你不想发送一份塞满默认值、又长又冗余的 JSON 响应。

8.1response_model_exclude_unset=True

在装饰器上设置response_model_exclude_unset=True后,那些默认值不会出现在响应里,只有真正被设置的字段才会输出。

例如向 ID 为foo的条目发起请求(该条目只存了nameprice),响应(不含默认值)将是:

{ "name": "Foo", "price": 50.2 }

提示:还可以配合使用

  • response_model_exclude_defaults=True
  • response_model_exclude_none=True

其语义分别对应 Pydantic 的exclude_defaultsexclude_none(按字段取值决定包含/排除哪些字段)。

字段带默认值、但数据里有值的情况

如果数据本身给这些带默认值的字段提供了值,例如 ID 为bar的条目:

{ "name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2 }

那么这些值被包含进响应。

数据值与默认值恰好相同的情况

再看 ID 为baz的条目:

{ "name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": [] }

这里的descriptiontaxtags与默认值完全相同。FastAPI(准确地说是 Pydantic)足够聪明,能分辨出这些字段是被显式设置的(而非取自默认值),因此它们依然被包含在 JSON 响应里。

提示:默认值不一定是None,可以是任意内容——空列表[]、浮点数10.5等都没问题。

8.2response_model_includeresponse_model_exclude

你还可以使用装饰器参数response_model_includeresponse_model_exclude。它们接收一个由属性名字符串构成的set:前者表示"只包含这些属性(其余省略)",后者表示"排除这些属性(其余包含)"。当你只有一个 Pydantic model、又想从输出里删掉部分字段时,这是快捷方式。

示例见 tutorial005_py310.py:

from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class Item(BaseModel): name: str description: str | None = None price: float tax: float = 10.5 items = { "foo": {"name": "Foo", "price": 50.2}, "bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2}, "baz": { "name": "Baz", "description": "There goes my baz", "price": 50.2, "tax": 10.5, }, } @app.get( "/items/{item_id}/name", response_model=Item, response_model_include={"name", "description"}, ) async def read_item_name(item_id: str): return items[item_id] @app.get("/items/{item_id}/public", response_model=Item, response_model_exclude={"tax"}) async def read_item_public_data(item_id: str): return items[item_id]

其中语法{"name", "description"}会生成包含这两个值的set,等价于set(["name", "description"])

建议:相比这些参数,仍然推荐使用前文"多类 + 继承"的思路。原因在于:即使你用response_model_include/response_model_exclude省略了某些属性,OpenAPI(及自动文档)中生成的 JSON Schema依然是完整模型的那一份——这与你的实际输出并不完全对应。该提醒同样适用于行为类似的response_model_by_alias

list代替set

如果你忘了用set而写成listtuple,FastAPI 仍会自动把list/tuple转成set并正常工作。示例见 tutorial006_py310.py,其路由定义如下:

@app.get( "/items/{item_id}/name", response_model=Item, response_model_include=["name", "description"], ) async def read_item_name(item_id: str): return items[item_id] @app.get("/items/{item_id}/public", response_model=Item, response_model_exclude=["tax"]) async def read_item_public_data(item_id: str): return items[item_id]

九、小结(Recap)

  • path operation decorator的参数response_model定义响应模型,尤其是确保隐私数据被过滤掉(例如不要把输入模型里的密码等字段直接回显给客户端)。
  • 当实际返回对象与输出结构不同(不同类)时,优先通过response_model+ 多模型/类继承的组合来兼得类型工具支持与 FastAPI 的自动字段过滤。
  • response_model_exclude_unset等编码参数只返回那些被显式设置的字段,从而避免响应被一堆默认值污染;需要精确裁剪字段时再使用response_model_include/response_model_exclude,并留意其对 OpenAPI Schema 完整性的影响。

结合本仓库的实现(参见 fastapi/routing.py 对相关参数的接收与 fastapi/routing.py、fastapi/routing.py 对 Pydantic 序列化的调用),可以看出"声明响应模型 → 校验 → 过滤 → 序列化"是一条被框架集中处理的主链路。把输入与输出模型分开设计、把过滤交给框架而非手写,是这套机制里最值得养成的习惯。

【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 20:24:22

9款Claude Code插件实测:从上下文压缩到自动化编排,好用才留

这两年 Claude Code 的火爆程度,相信不用我多说了。命令行里跑 AI 编程助手,已经从“极客玩具”变成了不少人日常工作的标配。但项目火了,插件生态自然也跟着热闹起来,GitHub 上随便一搜就是一大堆号称“提效十倍”的插件&#xf…

作者头像 李华
网站建设 2026/9/8 20:18:12

【单片机课程设计/毕业设计】基于 STM32 的 TDS 水质检测与阈值调控智能装置设计 基于 STM32 的蓝牙 APP 远程饮水监测控制系统设计(011807)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华