目錄
01-3. 路徑參數與查詢參數
⏱️ 閱讀時間: 15 分鐘 🎯 難度: ⭐ (入門)
🤔 一句話解釋
路徑參數是 URL 的一部分,查詢參數是 URL 後面的 ?key=value
https://api.example.com/users/123/posts?page=1&limit=10
↑ ↑ ↑
路徑參數 查詢參數 查詢參數📍 路徑參數 (Path Parameters)
路徑參數用來識別特定的資源。
基本用法
from fastapi import FastAPI
app = FastAPI()
# {user_id} 就是路徑參數
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return {"user_id": user_id}請求:
GET /users/123回應:
{"user_id": 123}型別自動轉換
FastAPI 會自動根據型別提示轉換參數:
@app.get("/items/{item_id}")
async def get_item(item_id: int): # 自動轉成 int
return {"item_id": item_id, "type": type(item_id).__name__}測試:
# 正確 - 會轉成 int
GET /items/42
# 回應: {"item_id": 42, "type": "int"}
# 錯誤 - 無法轉換
GET /items/abc
# 回應: 422 Unprocessable Entity
# {
# "detail": [{
# "loc": ["path", "item_id"],
# "msg": "Input should be a valid integer",
# "type": "int_parsing"
# }]
# }多個路徑參數
@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
}請求:
GET /users/123/posts/456路徑參數順序很重要!
# ⚠️ 順序錯誤的例子
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return {"user_id": user_id}
@app.get("/users/me") # 這個永遠不會被匹配到!
async def get_current_user():
return {"user": "current"}問題: /users/me 會被第一個路由匹配,me 會嘗試轉成 int 然後失敗。
正確做法:
# ✅ 固定路徑放前面
@app.get("/users/me")
async def get_current_user():
return {"user": "current"}
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return {"user_id": user_id}🔍 查詢參數 (Query Parameters)
查詢參數用於過濾、排序、分頁等。
基本用法
from fastapi import FastAPI
app = FastAPI()
@app.get("/items")
async def list_items(skip: int = 0, limit: int = 10):
return {
"skip": skip,
"limit": limit
}請求:
# 使用預設值
GET /items
# 回應: {"skip": 0, "limit": 10}
# 指定參數
GET /items?skip=20&limit=50
# 回應: {"skip": 20, "limit": 50}
# 只指定部分參數
GET /items?limit=5
# 回應: {"skip": 0, "limit": 5}必填 vs 選填
from typing import Optional
@app.get("/search")
async def search(
q: str, # 必填(沒有預設值)
page: int = 1, # 選填(有預設值)
size: int = 10, # 選填
sort: Optional[str] = None # 選填(明確標記可為 None)
):
return {
"query": q,
"page": page,
"size": size,
"sort": sort
}測試:
# 缺少必填參數
GET /search
# 回應: 422 Unprocessable Entity
# {"detail": [{"loc": ["query", "q"], "msg": "Field required"}]}
# 正確請求
GET /search?q=fastapi
# 回應: {"query": "fastapi", "page": 1, "size": 10, "sort": null}
# 完整請求
GET /search?q=fastapi&page=2&size=20&sort=date
# 回應: {"query": "fastapi", "page": 2, "size": 20, "sort": "date"}布林值參數
@app.get("/items")
async def list_items(
include_deleted: bool = False
):
return {"include_deleted": include_deleted}FastAPI 支援多種布林值格式:
# 以下都會被解析為 True
GET /items?include_deleted=true
GET /items?include_deleted=True
GET /items?include_deleted=1
GET /items?include_deleted=yes
GET /items?include_deleted=on
# 以下都會被解析為 False
GET /items?include_deleted=false
GET /items?include_deleted=False
GET /items?include_deleted=0
GET /items?include_deleted=no
GET /items?include_deleted=off列表參數
from typing import List
@app.get("/items")
async def list_items(tags: List[str] = []):
return {"tags": tags}請求:
GET /items?tags=python&tags=fastapi&tags=api
# 回應: {"tags": ["python", "fastapi", "api"]}🎛️ 使用 Query 進行驗證
Query 類別提供更多驗證選項:
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items")
async def list_items(
# 字串驗證
q: str = Query(
default=None,
min_length=3,
max_length=50,
pattern="^[a-zA-Z]+$", # 正規表達式
title="搜尋關鍵字",
description="用於搜尋的關鍵字,只能包含英文字母"
),
# 數字驗證
page: int = Query(
default=1,
ge=1, # >= 1
le=100, # <= 100
title="頁碼",
description="第幾頁,從 1 開始"
),
size: int = Query(
default=10,
gt=0, # > 0
lt=101, # < 101
title="每頁數量"
),
):
return {"q": q, "page": page, "size": size}Query 常用參數
| 參數 | 說明 | 範例 |
|---|---|---|
default | 預設值 | default=10 |
min_length | 最小長度 | min_length=3 |
max_length | 最大長度 | max_length=50 |
pattern | 正規表達式 | pattern="^[a-z]+$" |
ge | 大於等於 | ge=1 |
gt | 大於 | gt=0 |
le | 小於等於 | le=100 |
lt | 小於 | lt=101 |
title | 標題(文件用) | title="頁碼" |
description | 描述(文件用) | description="..." |
deprecated | 標記為棄用 | deprecated=True |
alias | 參數別名 | alias="item-query" |
必填查詢參數
from fastapi import Query
@app.get("/search")
async def search(
# 方法 1: 使用 ... (Ellipsis)
q: str = Query(..., min_length=1),
# 方法 2: 使用 Query(default=...)
# q: str = Query(default=..., min_length=1),
):
return {"query": q}參數別名
當 Python 變數名稱與實際參數名稱不同時:
@app.get("/items")
async def list_items(
# URL 用 item-query,程式碼用 item_query
item_query: str = Query(default=None, alias="item-query")
):
return {"query": item_query}請求:
GET /items?item-query=test
# 回應: {"query": "test"}🎛️ 使用 Path 進行驗證
Path 類別用於路徑參數的驗證:
from fastapi import FastAPI, Path
app = FastAPI()
@app.get("/items/{item_id}")
async def get_item(
item_id: int = Path(
..., # 必填
ge=1, # >= 1
le=1000, # <= 1000
title="項目 ID",
description="項目的唯一識別碼"
)
):
return {"item_id": item_id}參數順序問題
Python 要求有預設值的參數必須在沒有預設值的參數後面:
# ❌ 錯誤:有預設值的參數放前面
@app.get("/items/{item_id}")
async def get_item(
q: str = None, # 有預設值
item_id: int # 沒有預設值 → SyntaxError!
):
pass
# ✅ 正確:使用 * 讓順序無所謂
@app.get("/items/{item_id}")
async def get_item(
*, # 之後的都是 keyword-only
item_id: int, # 可以放前面
q: str = None
):
return {"item_id": item_id, "q": q}
# ✅ 正確:使用 Path() 並給預設值
@app.get("/items/{item_id}")
async def get_item(
q: str = None,
item_id: int = Path(..., ge=1) # ... 表示必填
):
return {"item_id": item_id, "q": q}🔀 路徑參數 + 查詢參數
實際應用中常常混合使用:
from fastapi import FastAPI, Path, Query
from typing import Optional
app = FastAPI()
@app.get("/users/{user_id}/posts")
async def get_user_posts(
# 路徑參數
user_id: int = Path(..., ge=1, description="使用者 ID"),
# 查詢參數
skip: int = Query(0, ge=0, description="跳過的數量"),
limit: int = Query(10, ge=1, le=100, description="返回的數量"),
status: Optional[str] = Query(None, description="文章狀態過濾"),
tags: list[str] = Query([], description="標籤過濾"),
):
return {
"user_id": user_id,
"skip": skip,
"limit": limit,
"status": status,
"tags": tags
}請求範例:
GET /users/123/posts?skip=10&limit=20&status=published&tags=python&tags=fastapi回應:
{
"user_id": 123,
"skip": 10,
"limit": 20,
"status": "published",
"tags": ["python", "fastapi"]
}🎨 實戰範例:商品搜尋 API
from fastapi import FastAPI, Query, Path
from typing import Optional
from enum import Enum
app = FastAPI()
class SortOrder(str, Enum):
asc = "asc"
desc = "desc"
class Category(str, Enum):
electronics = "electronics"
clothing = "clothing"
food = "food"
books = "books"
@app.get("/products")
async def search_products(
# 搜尋關鍵字
q: Optional[str] = Query(
None,
min_length=2,
max_length=100,
description="搜尋關鍵字"
),
# 分類過濾(使用 Enum)
category: Optional[Category] = Query(
None,
description="商品分類"
),
# 價格範圍
min_price: float = Query(
0,
ge=0,
description="最低價格"
),
max_price: Optional[float] = Query(
None,
ge=0,
description="最高價格"
),
# 排序
sort_by: str = Query(
"created_at",
description="排序欄位"
),
order: SortOrder = Query(
SortOrder.desc,
description="排序方向"
),
# 分頁
page: int = Query(1, ge=1, description="頁碼"),
per_page: int = Query(20, ge=1, le=100, description="每頁數量"),
# 布林過濾
in_stock: bool = Query(True, description="只顯示有庫存"),
on_sale: bool = Query(False, description="只顯示特價商品"),
):
"""
搜尋商品
支援多種過濾和排序選項:
- 關鍵字搜尋
- 分類過濾
- 價格範圍
- 排序
- 分頁
"""
return {
"filters": {
"q": q,
"category": category,
"min_price": min_price,
"max_price": max_price,
"in_stock": in_stock,
"on_sale": on_sale,
},
"sort": {
"by": sort_by,
"order": order
},
"pagination": {
"page": page,
"per_page": per_page,
"total": 100, # 假設總共 100 筆
"pages": 5
},
"results": [] # 實際的搜尋結果
}
@app.get("/products/{product_id}")
async def get_product(
product_id: int = Path(
...,
ge=1,
title="商品 ID",
description="商品的唯一識別碼"
),
include_reviews: bool = Query(
False,
description="是否包含評論"
),
include_related: bool = Query(
False,
description="是否包含相關商品"
)
):
"""獲取單一商品詳情"""
return {
"product_id": product_id,
"include_reviews": include_reviews,
"include_related": include_related
}✅ 重點總結
路徑參數 vs 查詢參數
| 特性 | 路徑參數 | 查詢參數 |
|---|---|---|
| 位置 | URL 路徑中 | ? 後面 |
| 用途 | 識別資源 | 過濾、排序、分頁 |
| 必填 | 通常必填 | 可選填 |
| 語法 | /{param} | ?key=value |
| 範例 | /users/123 | /users?role=admin |
何時用哪個?
┌─────────────────────────────────────────────┐
│ 這個參數是用來「識別唯一資源」嗎? │
│ │
│ 是 → 用路徑參數 │
│ GET /users/{user_id} │
│ GET /posts/{post_id} │
│ │
│ 否 → 用查詢參數 │
│ GET /users?role=admin&active=true │
│ GET /posts?page=1&limit=10 │
└─────────────────────────────────────────────┘驗證工具
from fastapi import Path, Query
# 路徑參數驗證
item_id: int = Path(..., ge=1, le=1000)
# 查詢參數驗證
page: int = Query(1, ge=1, le=100)
q: str = Query(..., min_length=3, max_length=50)🎤 面試這樣答
Q: 路徑參數和查詢參數有什麼區別?何時使用?
答案:
路徑參數用於識別特定資源,是 URL 的一部分,通常是必填的。例如
/users/123中的123用來識別特定使用者。查詢參數用於過濾、排序、分頁等操作,在 URL 的
?後面,通常是選填的。例如/users?role=admin&page=1。選擇原則:
- 如果少了這個參數,這個 URL 就沒有意義 → 路徑參數
- 如果這個參數是用來修飾/過濾結果 → 查詢參數
🤓 小測驗
/users/{id}中的{id}是什麼?/items?page=1&limit=10中的page和limit是什麼?如何讓查詢參數變成必填?
如何限制數字參數必須大於 0?
🏋️ 練習作業
建立一個「電影搜尋 API」:
GET /movies- 搜尋電影q: 搜尋關鍵字(選填,至少 2 字)genre: 類型過濾(選填)year_from: 年份起始(選填)year_to: 年份結束(選填)sort: 排序欄位(rating, year, title)page: 頁碼(預設 1)limit: 每頁數量(預設 20,最大 100)
GET /movies/{movie_id}- 獲取電影詳情movie_id: 電影 ID(必須 > 0)include_cast: 是否包含演員資訊
上一篇: 01-2. 環境設定與第一個 API 下一篇: 01-4. 請求體與回應模型
最後更新:2025-12-17