路径参数与查询参数

写给初学者:每个概念都从”为什么需要它”开始讲,配合通俗比喻和完整可运行代码。


一、路径参数(Path Parameters)

1.1 什么是路径参数?

就是路径里变化的那部分。比如:

 
/users/1      → 获取用户 1 的信息
 
/users/2      → 获取用户 2 的信息
 
/users/999    → 获取用户 999 的信息
 

这里的 12999 就是路径参数。

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=10offset=0category=手机 这些就是查询参数。

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} 条数据"
 
    }
 

各种访问方式:

请求skiplimit说明
/users010都用默认值
/users?skip=202010只传 skip
/users?limit=505只传 limit
/users?skip=20&limit=5205都传

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)

OptionalNone 作默认值:

 
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}
 

以下值都会被识别为 TruetrueTrue1onyes

以下值都会被识别为 FalsefalseFalse0offno

请求discounted
/itemsFalse
/items?discounted=trueTrue
/items?discounted=1True
/items?discounted=noFalse

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 是选填,无值必填跑不了。

相关链接


技术学习路线图 > 基础篇