FastAPI: 响应模型 — 精准控制 API 输出
最后更新:2026-08-26
响应模型就像舞台灯光——只照亮该让观众看到的区域,后台设备(内部字段)留在暗处,既美观又安全。
1. 你将学到
response_model基础:自动过滤未声明字段、序列化控制response_model_exclude/response_model_include:字段级精细过滤- 多响应模型:同一端点返回不同模型(
Union/列表响应) response_model_by_alias与response_model_exclude_unset:实战技巧- Alice 场景:PriceTracker 价格响应——公开版(隐藏成本价)vs 管理版(完整价格链)
2. Alice 的真实故事
(1) 痛点:成本价泄露给竞争对手
Alice 的 PriceTracker 存储了商品的零售价和批发价。某天 Bob 前端请求商品详情时,API 把批发价也返回了,竞争对手抓取 API 后获得了所有批发价信息,Alice 的客户非常不满。问题根源是 Flask 没有响应过滤机制,ORM 对象所有字段直接序列化返回。
(2) response_model 的解法
FastAPI 的 response_model 声明式过滤——只返回模型中声明的字段,未声明的字段自动剔除,从架构层面杜绝数据泄露。
PYTHON
from fastapi import FastAPI
from pydantic import BaseModel
class ProductPublic(BaseModel):
id: int
name: str
retail_price: float # Only retail price
class ProductAdmin(BaseModel):
id: int
name: str
retail_price: float
wholesale_price: float # Includes wholesale
app = FastAPI()
@app.get("/products/{id}", response_model=ProductPublic)
async def get_product_public(id: int):
# Even if DB has wholesale_price, response_model filters it out
return {"id": id, "name": "Widget", "retail_price": 29.99, "wholesale_price": 15.0}
(3) 收益
响应过滤从"记得手动删除字段"变成"声明模型自动过滤",批发价泄露问题彻底消失。公开 API 只返回 ProductPublic 声明的字段,管理 API 用 ProductAdmin 返回完整数据。
3. response_model 基础
(1) 自动过滤原理
flowchart LR
A[ORM Object] --> B{response_model}
B -->|Declared fields| C[JSON Response]
B -->|Undeclared fields| D[Filtered Out]
subgraph ProductPublic
E[id]
F[name]
G[retail_price]
end
subgraph Hidden
H[wholesale_price]
I[cost_price]
J[supplier_id]
end
▶ 示例:response_model 自动过滤
PYTHON
from fastapi import FastAPI
from pydantic import BaseModel
class ProductResponse(BaseModel):
id: int
name: str
category: str
# Simulated database record with extra fields
DB_PRODUCT = {
"id": 1,
"name": "Widget",
"category": "electronics",
"cost_price": 8.50, # Should NOT be in response
"supplier_id": 42, # Should NOT be in response
}
app = FastAPI()
@app.get("/products/{product_id}", response_model=ProductResponse)
async def get_product(product_id: int):
# cost_price and supplier_id are auto-filtered
return DB_PRODUCT
输出:
TEXT
📖 仅展示
{"id": 1, "name": "Widget", "category": "electronics"}
(2) response_model 与返回类型对比
| 方式 | 过滤行为 | 文档生成 | 推荐场景 |
|---|---|---|---|
response_model=X |
自动过滤 | 是 | 始终推荐 |
返回类型 -> X |
不过滤 | 是 | 仅类型标注 |
| 无声明 | 不过滤 | 无 | 不推荐 |
▶ 示例:response_model vs 返回类型
PYTHON
from fastapi import FastAPI
from pydantic import BaseModel
class ProductBrief(BaseModel):
id: int
name: str
app = FastAPI()
@app.get("/demo/model", response_model=ProductBrief)
async def with_response_model():
# response_model FILTERS: only id and name in response
return {"id": 1, "name": "Widget", "secret": "hidden"}
@app.get("/demo/type") -> ProductBrief
async def with_return_type():
# Return type does NOT filter: secret field leaks!
return {"id": 1, "name": "Widget", "secret": "leaked"}
输出:
TEXT
📖 仅展示
# 函数定义成功
4. 字段级精细过滤
(1) exclude 与 include
当不想为每种场景都创建新模型时,可用 response_model_exclude 和 response_model_include 动态过滤字段。
▶ 示例:exclude 排除敏感字段
PYTHON
from fastapi import FastAPI
from pydantic import BaseModel, Field
class ProductFull(BaseModel):
id: int
name: str
retail_price: float
wholesale_price: float = Field(exclude=True) # Default exclude
cost_price: float = Field(exclude=True)
supplier: str = Field(exclude=True)
app = FastAPI()
@app.get("/products/{id}", response_model=ProductFull)
async def get_product(id: int):
return {
"id": id,
"name": "Widget",
"retail_price": 29.99,
"wholesale_price": 15.0,
"cost_price": 8.50,
"supplier": "Acme Corp",
}
输出:
TEXT
📖 仅展示
INFO: 127.0.0.1:50123 - "GET /api/items HTTP/1.1" 200 OK
输出(wholesale_price、cost_price、supplier 被过滤):
TEXT
📖 仅展示
{"id": 1, "name": "Widget", "retail_price": 29.99}
▶ 示例:response_model_exclude 动态排除
PYTHON
from fastapi import FastAPI
from pydantic import BaseModel
class ProductFull(BaseModel):
id: int
name: str
retail_price: float
wholesale_price: float
cost_price: float
app = FastAPI()
@app.get(
"/products/{id}",
response_model=ProductFull,
response_model_exclude={"wholesale_price", "cost_price"},
)
async def get_product_public(id: int):
return {
"id": id, "name": "Widget",
"retail_price": 29.99, "wholesale_price": 15.0, "cost_price": 8.50,
}
@app.get(
"/admin/products/{id}",
response_model=ProductFull,
)
async def get_product_admin(id: int):
return {
"id": id, "name": "Widget",
"retail_price": 29.99, "wholesale_price": 15.0, "cost_price": 8.50,
}
输出:
TEXT
📖 仅展示
# 函数定义成功
| 过滤方式 | 适用场景 | 粒度 |
|---|---|---|
response_model=ModelA |
不同角色看不同模型 | 模型级 |
response_model_exclude={fields} |
排除少量字段 | 字段级 |
response_model_include={fields} |
只包含少量字段 | 字段级 |
Field(exclude=True) |
固定排除某个字段 | 字段定义级 |
5. 多响应模型与高级技巧
(1) 同一端点不同响应
▶ 示例:Union 响应模型
PYTHON
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Union
class ProductFound(BaseModel):
id: int
name: str
price: float
class ProductNotFound(BaseModel):
error: str
product_id: int
app = FastAPI()
@app.get("/products/{id}", response_model=Union[ProductFound, ProductNotFound])
async def search_product(id: int):
if id == 1:
return ProductFound(id=1, name="Widget", price=9.99)
return ProductNotFound(error="Not found", product_id=id)
输出:
TEXT
📖 仅展示
# 函数定义成功
▶ 示例:列表响应模型
PYTHON
from fastapi import FastAPI
from pydantic import BaseModel
class PriceEntry(BaseModel):
product_id: int
price: float
recorded_at: str
app = FastAPI()
@app.get("/products/{id}/prices", response_model=list[PriceEntry])
async def get_price_history(id: int):
return [
{"product_id": id, "price": 29.99, "recorded_at": "2026-01-01"},
{"product_id": id, "price": 24.99, "recorded_at": "2026-02-01"},
{"product_id": id, "price": 34.99, "recorded_at": "2026-03-01"},
]
输出:
TEXT
📖 仅展示
# 函数定义成功
(2) exclude_unset 与 exclude_none
▶ 示例:exclude_unset 只返回有值的字段
PYTHON
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional
class ProductUpdate(BaseModel):
name: Optional[str] = None
category: Optional[str] = None
price: Optional[float] = None
app = FastAPI()
@app.get(
"/products/{id}",
response_model=ProductUpdate,
response_model_exclude_unset=True,
)
async def get_product_partial(id: int):
# Only name was set, category and price remain None
return ProductUpdate(name="Updated Widget")
输出:
TEXT
📖 仅展示
INFO: 127.0.0.1:50123 - "GET /api/items HTTP/1.1" 200 OK
输出(只包含设置了值的字段):
TEXT
📖 仅展示
{"name": "Updated Widget"}
| 选项 | 效果 | 适用场景 |
|---|---|---|
response_model_exclude_unset=True |
排除未设置的字段 | PATCH 响应,只返回更新的字段 |
response_model_exclude_none=True |
排除值为 None 的字段 | 精简响应,去除空值 |
response_model_exclude_defaults=True |
排除使用默认值的字段 | 去除冗余默认值 |
response_model_by_alias=True |
用别名输出字段 | 前端需要驼峰命名 |
▶ 示例:response_model_by_alias
PYTHON
from fastapi import FastAPI
from pydantic import BaseModel, Field, ConfigDict
class ProductResponse(BaseModel):
model_config = ConfigDict(populate_by_name=True)
id: int
product_name: str = Field(alias="productName")
unit_price: float = Field(alias="unitPrice", description="Price in USD")
app = FastAPI()
@app.get("/products/{id}", response_model=ProductResponse, response_model_by_alias=True)
async def get_product(id: int):
return {"id": id, "productName": "Widget", "unitPrice": 9.99}
输出:
TEXT
📖 仅展示
INFO: 127.0.0.1:50123 - "GET /api/items HTTP/1.1" 200 OK
输出(字段名使用 alias):
TEXT
📖 仅展示
{"id": 1, "productName": "Widget", "unitPrice": 9.99}
❓ 常见问题
Q response_model 和返回类型注解有什么区别?
A response_model 会过滤未声明字段并校验输出,返回类型注解仅影响 OpenAPI 文档生成,不会过滤字段。始终用 response_model。
Q exclude 和 include 能同时用吗?
A 不能同时使用。选其一即可,exclude 排除指定字段,include 只包含指定字段。
Q exclude_unset 在什么场景最有用?
A PATCH 请求的响应——客户端只更新了部分字段,响应也只返回被更新的字段,避免混淆。
Q 嵌套模型的字段能被 exclude 吗?
A 可以,用点号路径
{"nested_model": {"secret_field"}} 排除嵌套字段。Q 列表响应怎么声明?
A 用
response_model=list[ItemModel],FastAPI 会自动将列表中每个元素按模型过滤。Q response_model 会影响性能吗?
A 有轻微开销(序列化+过滤),但换来的是数据安全和文档一致性。百万级 QPS 场景可用 orjson 优化序列化。
📖 小节
response_model自动过滤未声明字段,从架构层面防止敏感数据泄露response_model_exclude/response_model_include实现字段级精细控制,无需创建新模型Union响应模型支持同一端点返回不同结构,列表响应用list[Model]response_model_exclude_unset只返回设置了值的字段,适合 PATCH 场景response_model_by_alias=True让响应使用别名,满足前端驼峰命名需求
📝 作业
- 基础题(难度⭐):创建
ProductPublic模型(id, name, price),用response_model让端点只返回这三个字段,即使返回 dict 含有cost_price也不泄露。提示:@app.get(..., response_model=ProductPublic) - 进阶题(难度⭐⭐):为 PriceTracker 创建两个端点——公开端点隐藏
wholesale_price,管理端点显示完整字段。用response_model_exclude实现。提示:response_model_exclude={"wholesale_price"} - 挑战题(难度⭐⭐⭐):创建
ProductDetail模型含可选字段(description, image_url 等),用response_model_exclude_unset=True让端点只返回客户端提供的字段,空值不出现在 JSON 中。提示:Optional[str] = None+response_model_exclude_unset=True
---|