Python Pydantic v2 完全教學:資料驗證、模型設計與 FastAPI 整合實戰
為什麼 Pydantic v2 是 Python 開發者的必學技能?
如果你常用 FastAPI 或處理任何形式的資料輸入驗證,Pydantic 幾乎是你每天都會碰到的工具。v2 版本在 2023 年正式發布,底層核心從純 Python 改寫成 Rust(透過 pydantic-core),效能提升幅度高達 5 到 50 倍,同時 API 設計也做了相當程度的優化。這不只是小版本升級,而是一次幾乎全面重寫的大改版。
本篇教學會帶你從 BaseModel 的基礎用法開始,逐步深入 Field 驗證、自訂驗證器、model_validator,最後整合到 FastAPI 的實際場景。如果你正在使用 FastAPI 開發即時通訊應用,Pydantic v2 的新特性會讓你的請求解析與資料安全更加穩固。
Pydantic v2 核心變化總覽
在開始寫程式碼之前,先了解 v2 和 v1 的關鍵差異,避免踩坑:
- Rust 底層(pydantic-core):驗證效能大幅提升,特別是在大量資料解析時感受明顯
model_validator取代root_validator:語意更清晰,支援mode='before'和mode='after'field_validator取代validator:裝飾器語法更直觀model_config取代class Config:使用ConfigDict集中管理設定- 嚴格模式(strict mode):可以禁止隱式型別轉換,讓資料更可預測
BaseModel 基礎用法
Pydantic 的核心就是繼承 BaseModel 來定義資料結構。v2 的語法和 v1 相差不大,但背後的驗證邏輯更嚴格:
from pydantic import BaseModel, EmailStr
from typing import Optional
from datetime import datetime
class UserCreate(BaseModel):
username: str
email: EmailStr
age: int
bio: Optional[str] = None
created_at: datetime = datetime.now()
# 正常建立
user = UserCreate(
username="zhiqing",
email="zhiqing@example.com",
age=28
)
print(user.model_dump()) # v2 用 model_dump(),v1 是 .dict()
# 錯誤輸入會拋出 ValidationError
try:
bad_user = UserCreate(username="test", email="not-an-email", age="abc")
except Exception as e:
print(e) # 清楚列出每個欄位的驗證錯誤
注意 v2 的方法名稱變化:.dict() 改為 .model_dump(),.json() 改為 .model_dump_json(),.schema() 改為 .model_json_schema()。這些舊方法在 v2 仍可用但已標記為 deprecated。
Field:細粒度欄位控制
當你需要對欄位加上額外限制或中繼資料時,Field 是你的好朋友:
from pydantic import BaseModel, Field
from typing import Annotated
class ProductCreate(BaseModel):
name: Annotated[str, Field(min_length=2, max_length=100)]
price: Annotated[float, Field(gt=0, description="商品售價(新台幣)")]
stock: Annotated[int, Field(ge=0, default=0)]
sku: str = Field(
pattern=r'^[A-Z]{2}\d{6}$',
examples=["AB123456"],
description="庫存單位,格式:2大寫英文 + 6數字"
)
# v2 推薦用 Annotated 搭配 Field,語意更清楚
product = ProductCreate(name="Python 教學書", price=450.0, sku="PY000001")
print(product.model_dump())
在 v2 中,Annotated 和 Field 的組合是官方推薦寫法,讓型別提示和驗證邏輯分離得更乾淨。
field_validator:自訂欄位驗證邏輯
遇到內建驗證規則無法涵蓋的情境,就用 field_validator:
from pydantic import BaseModel, field_validator
import re
class UserProfile(BaseModel):
username: str
phone: str
password: str
@field_validator('username')
@classmethod
def username_must_be_alphanumeric(cls, v: str) -> str:
if not re.match(r'^[a-zA-Z0-9_]+$', v):
raise ValueError('使用者名稱只能包含英文、數字與底線')
return v.lower() # 統一轉小寫儲存
@field_validator('phone')
@classmethod
def validate_tw_phone(cls, v: str) -> str:
# 台灣手機號碼格式驗證
pattern = r'^09\d{8}$'
if not re.match(pattern, v.replace('-', '').replace(' ', '')):
raise ValueError('請輸入有效的台灣手機號碼(09xxxxxxxx)')
return v
@field_validator('password')
@classmethod
def password_strength(cls, v: str) -> str:
if len(v) < 8:
raise ValueError('密碼至少需要 8 個字元')
if not any(c.isupper() for c in v):
raise ValueError('密碼需包含至少一個大寫英文字母')
return v
model_validator:跨欄位驗證
當驗證邏輯需要同時參考多個欄位時,model_validator 是正確工具。v2 的 mode 參數讓你選擇在資料解析前(before)或後(after)執行:
from pydantic import BaseModel, model_validator
from typing import Self
from datetime import date
class BookingRequest(BaseModel):
check_in: date
check_out: date
guests: int
room_type: str
discount_code: str = ""
final_price: float = 0.0
@model_validator(mode='after')
def validate_dates_and_price(self) -> Self:
# 驗證退房日必須晚於入住日
if self.check_out <= self.check_in:
raise ValueError('退房日期必須晚於入住日期')
# 計算住宿天數
nights = (self.check_out - self.check_in).days
base_prices = {'standard': 2800, 'deluxe': 4500, 'suite': 8800}
base = base_prices.get(self.room_type, 2800)
# 套用折扣碼
discount = 0.9 if self.discount_code == 'MEMBER10' else 1.0
self.final_price = round(base * nights * discount, 0)
return self
這種跨欄位驗證在訂房、電商結帳等場景非常實用,比用普通函式驗證更容易整合到資料流中。
與 FastAPI 整合實戰
Pydantic v2 和 FastAPI 的整合幾乎是無縫的,因為 FastAPI 本身就以 Pydantic 為核心。以下是一個完整的 CRUD API 範例,展示如何善用 Pydantic 的模型繼承:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field, EmailStr
from typing import Optional
from datetime import datetime
import uuid
app = FastAPI()
# Request schema(建立用,不含 id 和 created_at)
class ArticleCreate(BaseModel):
title: str = Field(min_length=5, max_length=200)
content: str = Field(min_length=100)
author_email: EmailStr
tags: list[str] = Field(default_factory=list, max_length=10)
# Response schema(回傳用,包含完整資訊)
class ArticleResponse(ArticleCreate):
id: str
created_at: datetime
view_count: int = 0
model_config = {"from_attributes": True} # 支援 ORM 模式
# 模擬資料庫
db: dict[str, dict] = {}
@app.post("/articles", response_model=ArticleResponse, status_code=201)
async def create_article(article: ArticleCreate):
article_id = str(uuid.uuid4())
record = {
**article.model_dump(),
"id": article_id,
"created_at": datetime.now(),
"view_count": 0
}
db[article_id] = record
return ArticleResponse(**record)
@app.get("/articles/{article_id}", response_model=ArticleResponse)
async def get_article(article_id: str):
if article_id not in db:
raise HTTPException(status_code=404, detail="文章不存在")
return ArticleResponse(**db[article_id])
使用分離的 Create/Response schema 是 FastAPI 的最佳實踐,可以避免把敏感欄位(如密碼雜湊)意外回傳給客戶端。這個模式在用 FastAPI 搭建 WebSocket 即時通訊時同樣適用。
ConfigDict:集中管理模型設定
v2 把 class Config 替換為 model_config,使用 ConfigDict 型別,IntelliSense 支援更好:
from pydantic import BaseModel, ConfigDict
class StrictModel(BaseModel):
model_config = ConfigDict(
strict=True, # 嚴格模式:禁止隱式型別轉換
str_strip_whitespace=True, # 自動去除字串首尾空白
frozen=True, # 不可變模型(類似 dataclass frozen)
from_attributes=True, # 支援從 ORM 物件建立(原 orm_mode)
populate_by_name=True, # 允許用欄位名稱或別名填入
)
name: str
count: int
# 嚴格模式下,字串 '123' 無法自動轉為 int
try:
m = StrictModel(name="test", count="123") # 這會報錯
except Exception as e:
print("嚴格模式驗證失敗:", e)
# 必須傳入正確型別
m = StrictModel(name="test", count=123) # OK
從 v1 遷移到 v2:常見改動清單
如果你有既有的 v1 程式碼需要遷移,以下是最常見的改動:
.dict()→.model_dump().json()→.model_dump_json().schema()→.model_json_schema()@validator→@field_validator(需加@classmethod)@root_validator→@model_validator(mode='before'/'after')class Config→model_config = ConfigDict(...)orm_mode = True→from_attributes=Trueschema_extra→json_schema_extra
官方也提供了 pydantic.v1 相容層,讓你可以在同一專案中混用 v1 和 v2,方便漸進式遷移。如果你同時在用 Python uv 管理套件依賴,可以用 uv 快速切換不同版本的 Pydantic 進行測試。
Pydantic v2 在資料工程的應用
Pydantic 不只用在 Web API,在資料處理管線中也非常有用。如果你有在用 Polars 處理大型 DataFrame,可以用 Pydantic 驗證讀入的原始資料,確保每一列都符合預期格式,再交給 Polars 進行分析運算。
from pydantic import BaseModel, field_validator
from typing import Any
class SalesRecord(BaseModel):
date: str
product_id: str
quantity: int
unit_price: float
region: str
@field_validator('quantity')
@classmethod
def quantity_positive(cls, v: int) -> int:
if v <= 0:
raise ValueError(f'銷售數量必須大於 0,收到:{v}')
return v
@property
def total_amount(self) -> float:
return self.quantity * self.unit_price
# 批次驗證 CSV 資料
def validate_sales_batch(raw_records: list[dict[str, Any]]) -> tuple[list[SalesRecord], list[str]]:
valid, errors = [], []
for i, record in enumerate(raw_records):
try:
valid.append(SalesRecord(**record))
except Exception as e:
errors.append(f"第 {i+1} 行驗證失敗:{e}")
return valid, errors
小結:v2 值得升級嗎?
答案是肯定的。Pydantic v2 的效能提升在高流量 API 場景中非常顯著,API 設計也更加一致和直觀。遷移成本主要集中在驗證器語法的調整,只要掌握本文整理的改動清單,大多數專案可以在幾小時內完成遷移。
搭配 FastAPI 使用時,v2 的嚴格模式和更完整的 JSON Schema 輸出,讓 API 文件(Swagger UI)更精準,也更容易和前端或其他服務對接。想進一步提升 Python 應用的非同步效能,可以參考 Python asyncio 非同步程式設計教學,搭配 Pydantic v2 打造更穩健的後端架構。
更多 Python 開發技巧,歡迎回到 Python 自動化入門完全指南,從基礎到進階一次掌握。
繼續閱讀
Python Pydantic V2 資料驗證完整教學:從 BaseModel 到自訂驗證器實戰指南
相關文章
你可能也喜歡
探索其他領域的精選好文