路径参数与查询参数
写给初学者:每个概念都从”为什么需要它”开始讲,配合通俗比喻和完整可运行代码。
一、路径参数(Path Parameters)
1.1 什么是路径参数?
就是路径里变化的那部分。比如:
/users/1 → 获取用户 1 的信息
/users/2 → 获取用户 2 的信息
/users/999 → 获取用户 999 的信息
这里的 1、2、999 就是路径参数。
1.2 基本用法
from fastapi import FastAPI
app = FastAPI()
# : int 是类型注解,FastAPI 会自动把路径里的字符串转成整数
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return {"user_id": user_id, "msg": f"你查询的是用户 {user_id}"}
访问 http://127.0.0.1:8000/users/42:
{"user_id": 42, "msg": "你查询的是用户 42"}
访问 http://127.0.0.1:8000/users/abc:
{
"detail": [
{
"type": "int_parsing",
"loc": ["path", "user_id"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "abc"
}
]
}
FastAPI 自动帮你做了类型校验!
user_id声明了int,传了字符串就自动报 422 错误。你不用写任何校验代码。
1.3 多个路径参数
@app.get("/users/{user_id}/posts/{post_id}")
async def get_user_post(user_id: int, post_id: int):
return {
"user_id": user_id,
"post_id": post_id,
"msg": f"获取用户 {user_id} 的第 {post_id} 篇文章"
}
访问 /users/3/posts/7:
{"user_id": 3, "post_id": 7, "msg": "获取用户 3 的第 7 篇文章"}
1.4 路径参数的顺序问题
固定路径必须放在可变路径前面,否则会被覆盖:
# ❌ 错误写法:/users/me 会被 /users/{user_id} 匹配到
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return {"user_id": user_id}
@app.get("/users/me")
async def get_me():
return {"msg": "这是当前用户"}
# ✅ 正确写法:固定路径放前面
@app.get("/users/me")
async def get_me():
return {"msg": "这是当前用户"}
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return {"user_id": user_id}
规则:具体路径在前,模糊路径在后。
1.5 路径参数的类型转换
FastAPI 支持多种类型的自动转换:
# 整数
@app.get("/items/{item_id}")
async def get_item(item_id: int): ... # /items/42 → item_id=42
# 浮点数
@app.get("/prices/{price}")
async def get_price(price: float): ... # /prices/9.99 → price=9.99
# 字符串(默认,不写类型也是 str)
@app.get("/tags/{tag}")
async def get_tag(tag: str): ... # /tags/python → tag="python"
# UUID
from uuid import UUID
@app.get("/orders/{order_id}")
async def get_order(order_id: UUID): ...
# /orders/550e8400-e29b-41d4-a716-446655440000 → 自动解析为 UUID 对象
二、查询参数(Query Parameters)
2.1 什么是查询参数?
就是跟在 ? 后面的键值对:
/users?limit=10&offset=0 → 查询用户列表,第1页,每页10条
/items?category=手机&sort=price → 查询手机分类,按价格排序
/search?q=python&page=2 → 搜索 python,第2页
limit=10、offset=0、category=手机 这些就是查询参数。
2.2 基本用法
from fastapi import FastAPI
app = FastAPI()
# 有默认值的参数是可选的,没有默认值的参数是必填的
@app.get("/users")
async def list_users(skip: int = 0, limit: int = 10):
return {
"skip": skip,
"limit": limit,
"msg": f"从第 {skip} 条开始,返回 {limit} 条数据"
}
各种访问方式:
| 请求 | skip | limit | 说明 |
|---|---|---|---|
/users | 0 | 10 | 都用默认值 |
/users?skip=20 | 20 | 10 | 只传 skip |
/users?limit=5 | 0 | 5 | 只传 limit |
/users?skip=20&limit=5 | 20 | 5 | 都传 |
2.3 必填的查询参数
不给默认值就是必填的:
@app.get("/search")
async def search(q: str): # 没有默认值,必须传
return {"query": q}
| 请求 | 结果 |
|---|---|
/search?q=python | ✅ {"query": "python"} |
/search | ❌ 422 错误:field required |
2.4 可选参数(Optional)
用 Optional 或 None 作默认值:
from typing import Optional
@app.get("/items")
async def list_items(
category: Optional[str] = None, # 可选,不传就是 None
min_price: float = 0.0, # 可选,不传就是 0.0
max_price: Optional[float] = None # 可选,不传就是 None
):
result = {"category": category, "min_price": min_price, "max_price": max_price}
return result
| 请求 | 结果 |
|---|---|
/items | {"category": null, "min_price": 0.0, "max_price": null} |
/items?category=手机 | {"category": "手机", "min_price": 0.0, "max_price": null} |
/items?min_price=100&max_price=500 | {"category": null, "min_price": 100.0, "max_price": 500.0} |
2.5 bool 类型的查询参数
FastAPI 会自动把字符串转成布尔值:
@app.get("/items")
async def list_items(discounted: bool = False):
return {"discounted": discounted}
以下值都会被识别为 True:true、True、1、on、yes
以下值都会被识别为 False:false、False、0、off、no
| 请求 | discounted |
|---|---|
/items | False |
/items?discounted=true | True |
/items?discounted=1 | True |
/items?discounted=no | False |
2.6 多个同名查询参数(列表参数)
有时候你需要传多个同名参数:/tags?tag=python&tag=fastapi
from typing import List, Optional
@app.get("/items")
async def list_items(tags: Optional[List[str]] = None):
return {"tags": tags}
| 请求 | 结果 |
|---|---|
/items | {"tags": null} |
/items?tag=python | {"tags": ["python"]} |
/items?tag=python&tag=fastapi | {"tags": ["python", "fastapi"]} |
速查表
| 概念 | 一句话解释 | 关键代码 |
|---|---|---|
| 路径参数 | URL 里变化的部分 | @app.get("/users/{user_id}") |
| 查询参数 | ?key=value 形式的参数 | def f(skip: int = 0) |
Optional | 字段可选(可以是 None) | name: Optional[str] = None |
速记卡(面试闪卡)
Q1:一句话讲清「路径参数与查询参数」到底是什么?
A:路径参数是 URL 路径中变化的部分(定位资源),查询参数是 ? 后键值对(筛选过滤)。
Q2:路径参数 Path(route segment) —— 怎么理解?
A:类比:路径参数像门牌号里变的那段:/users/{user_id} 中 42 就是。FastAPI 用类型注解自动把字符串转 int,传 abc 直接报 422(validation)。规则:固定路径(/users/me)必须写在可变路径前面,否则被覆盖。(Address variable)
Q3:查询参数 Query(query string) —— 怎么理解?
A:类比:查询参数像外卖单上的备注:?skip=20&limit=10。函数里没被路径占用的参数自动变成它,有默认值就可选、没默认值就必填(不传报 422)。bool 还能认 true/1/on/yes,列表参数 ?tag=a&tag=b 直接收成 List。(Order notes)
Q4:类型转换与校验(data validation) —— 怎么理解?
A:类比:FastAPI 像个严格但不啰嗦的前台:路径/查询参数声明 int、float、UUID、Optional,它自动转换并校验,错格式立刻 422 告诉你哪错了。你零行校验代码,全靠类型注解白嫖。(Free validation)
Q5:Optional 与必填(optional vs required) —— 怎么理解?
A:类比:Optional[str] = None 像”可填可不填的选填项”,不传就是 None;不写默认值就是”必填项”,漏了直接 422 field required。区分清必填和可选,接口契约(contract)才不会歧义。(Fill or not)
Q6:核心速记主线有哪些?
-
路径参数:URL 路径中变化部分,@app.get(“/users/{user_id}”),类型自动校验
-
查询参数:?key=value,函数剩余参数自动变查询参数,有默认值可选
-
固定路径放可变路径前,否则被模糊路由覆盖
-
类型注解驱动自动校验,错误返回 422;bool/List/UUID 都支持
-
Optional + 默认值 = 选填,无默认值 = 必填
口诀
A:路径参数门牌号,查询参数备注条;
固定路径在前头,模糊路由莫遮罩;
类型注解白嫖校验,错就 422 报;
Optional 是选填,无值必填跑不了。
相关链接
-
目录:00-FastAPI
-
下一篇:03-请求体与Pydantic