FastAPI 中路径参数、查询参数和请求体参数如何区分?
简化版
FastAPI 会根据参数声明自动判断来源:出现在路径模板里的参数是路径参数;基础类型且不在路径里的参数通常是查询参数;Pydantic 模型参数通常来自请求体。需要更明确控制时,可以使用 Path、Query、Body 等工具。
详细版
FastAPI 的参数来源规则很清晰:
/users/{user_id}中的user_id是路径参数;- 函数中未出现在路径里的基础类型参数,如
page: int = 1,通常是查询参数; - Pydantic
BaseModel类型通常表示请求体; - 多个 body 参数或特殊嵌套结构,可以用
Body显式声明; - 请求头和 Cookie 分别用
Header、Cookie声明。
示例:
@app.post("/users/{user_id}")
def update_user(
user_id: int,
verbose: bool = False,
user: UserUpdate = Body(),
):
return {"user_id": user_id, "verbose": verbose}
面试中要说明:FastAPI 的这种推断依赖类型注解和路径模板,但复杂场景最好显式声明,提升可读性。
完整版教学
一、为什么参数来源规则很重要
API 接口的输入可能来自很多地方:URL 路径、查询字符串、请求体、请求头、Cookie、文件表单。参数来源不清楚时,代码很容易变得混乱,接口文档也会含糊。
FastAPI 的优势是它能从函数签名推断大部分参数来源。例如:
@app.get("/users/{user_id}")
def get_user(user_id: int, detail: bool = False):
return {"user_id": user_id, "detail": detail}
访问 /users/1?detail=true 时,user_id 来自路径,detail 来自查询字符串。开发者不用手动从 request 对象里取。
二、路径参数如何识别
路径参数由路由模板决定。凡是在路径中用 {} 声明的变量,都必须在函数参数中出现:
@app.get("/orders/{order_id}")
def get_order(order_id: int):
return {"order_id": order_id}
order_id 会先从 URL 中取出,再根据类型注解转换成整数。如果转换失败,FastAPI 返回校验错误。
路径参数适合表达资源定位,例如用户 ID、订单 ID、文章 slug。它通常是必填的,因为没有路径参数就无法定位资源。
三、查询参数如何识别
函数参数没有出现在路径模板里,并且是基础类型,例如 str、int、bool、float,通常会被当作查询参数:
@app.get("/products")
def list_products(page: int = 1, size: int = 20, keyword: str | None = None):
return {"page": page, "size": size, "keyword": keyword}
访问方式是 /products?page=2&size=10&keyword=phone。
查询参数适合表达筛选、排序、分页、搜索关键词。它们不应该用于传递复杂业务对象,也不适合承载敏感信息,因为 URL 可能被日志、浏览器历史或代理记录。
四、请求体参数如何识别
如果参数是 Pydantic 模型,FastAPI 通常会把它视为请求体:
class ProductCreate(BaseModel):
name: str
price: float
@app.post("/products")
def create_product(product: ProductCreate):
return product
客户端需要发送 JSON:
{
"name": "Keyboard",
"price": 199.0
}
请求体适合创建或修改资源时提交结构化数据。POST、PUT、PATCH 常见请求体;GET 一般不建议依赖请求体。
五、什么时候显式使用 Path、Query、Body
虽然 FastAPI 能自动推断,但显式声明可以让接口意图更清楚,也能加约束:
from fastapi import Path, Query, Body
@app.put("/products/{product_id}")
def update_product(
product_id: int = Path(gt=0, description="商品 ID"),
keyword: str | None = Query(default=None, max_length=50),
product: ProductUpdate = Body(),
):
return {"product_id": product_id}
Path(gt=0) 表示路径 ID 必须大于 0。Query(max_length=50) 表示查询关键字最长 50。Body() 则明确说明该参数来自请求体。
在团队项目中,我更倾向对重要参数显式声明,因为这样文档更完整,维护者也更容易理解。
六、请求头、Cookie 和文件上传
FastAPI 还支持其他参数来源:
from fastapi import Header, Cookie
@app.get("/profile")
def profile(
user_agent: str | None = Header(default=None),
session_id: str | None = Cookie(default=None),
):
return {"user_agent": user_agent, "session_id": session_id}
文件上传使用 File 和 UploadFile:
from fastapi import File, UploadFile
@app.post("/upload")
async def upload(file: UploadFile = File()):
return {"filename": file.filename}
这些工具让参数来源显式化,避免把所有东西都塞进 Request 对象里手动解析。
七、常见误区与追问
| 参数来源 | 典型用途 | 例子 |
|---|---|---|
| 路径参数 | 定位唯一资源 | /users/42 中的 42 |
| 查询参数 | 过滤、分页、排序 | ?page=2&size=20 |
| 请求体 | 创建或修改结构化对象 | JSON 用户资料 |
| Header/Cookie | 元信息、认证凭证 | Authorization、session_id |
GET /products/100?detail=true
路径参数:product_id = 100
查询参数:detail = true
POST /products
请求体:{"name": "Keyboard", "price": 199.0}
记忆钩子:路径负责“找谁”,查询负责“怎么找”,请求体负责“提交什么”。
第一个误区是把所有参数都放到请求体。分页、筛选、排序更适合查询参数,因为它们描述的是“如何读取资源”,不是“提交一个资源对象”。
第二个误区是把敏感信息放到查询参数。token、密码、密钥不应该出现在 URL 里。
第三个误区是路径参数设计过深。比如 /a/{a_id}/b/{b_id}/c/{c_id}/d/{d_id} 可读性和维护性都不好,说明资源建模可能需要重新整理。
- 误区:把所有参数都放到请求体。 分页、排序、筛选更适合查询参数,因为它们描述读取资源的方式,不是提交资源本身。
- 误区:把密码、token、密钥放到查询参数。 URL 容易进入浏览器历史、网关日志、监控系统和 Referer,敏感凭证应放在 Header 或更合适的安全机制中。
- 误区:路径设计层级越深越 RESTful。
/a/{a_id}/b/{b_id}/c/{c_id}/d/{d_id}可读性差,通常说明资源边界需要重新建模。 - 追问:多个 Pydantic 模型参数会怎样? FastAPI 会把它们都视为 body 的不同字段;如果想控制嵌套结构,可以用
Body(embed=True)或显式请求体模型。 - 追问:为什么重要参数建议显式用
Path、Query、Body? 显式声明能补充范围、长度、描述和示例,让校验、文档和团队可读性都更稳定。 - 追问:GET 请求能不能带 body? HTTP 规范没有完全禁止,但生态支持和缓存代理语义都不稳定,面试和工程实践中通常不建议依赖 GET body。
八、加强记忆
记 FastAPI 参数来源规则:路径模板里出现的是路径参数,基础类型且不在路径里的是查询参数,Pydantic 模型通常是请求体。复杂或重要参数用 Path、Query、Body 显式声明,既能校验,也能生成更清楚的文档。