Python Pydantic V2 資料驗證完整教學:從 BaseModel 到自訂驗證器實戰指南
如果你寫 Python 寫了一陣子,應該都遇過那種「資料格式錯了但程式跑到一半才爆掉」的窘境。說真的,這種 bug 最難抓。Pydantic 就是來解決這個痛點的,而 V2 版本更是一次巨大的躍進。今天這篇文章,我會帶你從最基本的 BaseModel 開始,一路走到自訂驗證器,讓你徹底搞懂 Python Pydantic V2 資料驗證的核心觀念。
Pydantic V2 是什麼?為什麼該升級
Pydantic 是 Python 生態系中最受歡迎的資料驗證函式庫,被 FastAPI、LangChain 等主流框架採用。V2 版本在 2023 年正式發布,最大的改變是核心引擎用 Rust 重寫(pydantic-core),驗證速度提升了 5 到 50 倍不等。
除了效能以外,V2 也重新設計了 API,讓驗證器的寫法更直覺、型別提示更完善。如果你正在用 V1,我強烈建議盡早遷移,因為 V1 已經進入維護模式了。
BaseModel 基礎:定義你的第一個資料模型
一切的起點就是 BaseModel。你只需要繼承它,然後用 Python 的型別提示來定義欄位,Pydantic 就會自動幫你做型別驗證和資料轉換。
from pydantic import BaseModel
from typing import Optional
class User(BaseModel):
name: str
age: int
email: str
bio: Optional[str] = None
# 正常使用
user = User(name="Alice", age=28, email="[email protected]")
print(user.model_dump())
# {'name': 'Alice', 'age': 28, 'email': '[email protected]', 'bio': None}
# 自動型別轉換
user2 = User(name="Bob", age="30", email="[email protected]")
print(user2.age) # 30 (字串 "30" 被轉成 int)
注意到了嗎?age="30" 傳入字串,Pydantic 會自動嘗試轉型。這在處理前端送來的 JSON 資料時超級實用。但如果你傳入 age="abc",它就會拋出清楚的驗證錯誤。
V2 還有一個好用的功能是 model_json_schema(),可以直接產生 JSON Schema,跟前端溝通規格的時候特別方便。
field_validator:欄位級別的驗證邏輯
BaseModel 的預設驗證只管型別,但實際專案中你一定會有更細緻的規則。比如「年齡不能是負數」或「email 格式要正確」。這時候就需要 field_validator。
from pydantic import BaseModel, field_validator
class User(BaseModel):
name: str
age: int
email: str
@field_validator('age')
@classmethod
def age_must_be_positive(cls, v: int) -> int:
if v < 0 or v > 150:
raise ValueError('年齡必須在 0 到 150 之間')
return v
@field_validator('name')
@classmethod
def name_must_not_be_empty(cls, v: str) -> str:
if not v.strip():
raise ValueError('姓名不能為空白')
return v.strip()
幾個要注意的地方:V2 中 field_validator 必須加上 @classmethod 裝飾器,第一個參數是 cls 而非 self。如果你對裝飾器的運作機制不太熟悉,可以參考 Python 裝飾器 Decorator 從入門到進階這篇教學。
model_validator:跨欄位驗證的利器
有些驗證邏輯需要同時看多個欄位。比如「密碼」和「確認密碼」要一致,或者「開始日期」不能晚於「結束日期」。這時候 model_validator 就派上用場了。
from pydantic import BaseModel, model_validator
from datetime import date
class Event(BaseModel):
name: str
start_date: date
end_date: date
@model_validator(mode='after')
def check_dates(self) -> 'Event':
if self.start_date > self.end_date:
raise ValueError('開始日期不能晚於結束日期')
return self
class RegisterForm(BaseModel):
password: str
confirm_password: str
@model_validator(mode='after')
def passwords_match(self) -> 'RegisterForm':
if self.password != self.confirm_password:
raise ValueError('兩次輸入的密碼不一致')
return self
model_validator 有兩種模式:mode='before' 在欄位驗證之前執行,接收的是原始 dict;mode='after' 在所有欄位驗證通過後執行,接收的是已經建立好的 model 實例。我個人比較常用 after 模式,寫起來比較直覺。
V1 到 V2 遷移重點整理
如果你手上有 V1 的程式碼要遷移,以下是最常見的改動:
@validator改為@field_validator,並加上@classmethod@root_validator改為@model_validator.dict()改為.model_dump().json()改為.model_dump_json()Config內部類別改為model_config = ConfigDict(...)orm_mode = True改為from_attributes = True
from pydantic import ConfigDict, BaseModel
# V2 的設定方式
class User(BaseModel):
model_config = ConfigDict(
from_attributes=True,
str_strip_whitespace=True,
)
name: str
age: int
官方有提供 bump-pydantic 這個 CLI 工具,可以自動幫你做大部分的程式碼轉換,省下不少時間。
Rust 核心帶來的效能飛躍
V2 最令人興奮的改變就是底層用 Rust 重寫了驗證邏輯。這不是小幅優化,而是根本性的效能提升。根據官方的 benchmark,常見的驗證場景快了 5 到 50 倍。
這對一般的 CRUD 應用可能感受不太明顯,但在高併發的 API 服務中,驗證效能的差距就很可觀了。如果你正在用 Python FastAPI 建立 REST API,升級到 Pydantic V2 可以直接獲得免費的效能提升。
Rust 核心也讓錯誤訊息更結構化。V2 的 ValidationError 提供了 .errors() 方法,回傳格式統一的錯誤列表,方便你做 API 的錯誤回應處理。
實戰範例:API 請求資料驗證
最後來看一個比較完整的實戰範例,模擬一個電商場景中的訂單建立:
from pydantic import BaseModel, field_validator, model_validator
from typing import List
from decimal import Decimal
class OrderItem(BaseModel):
product_id: str
quantity: int
unit_price: Decimal
@field_validator('quantity')
@classmethod
def quantity_must_be_positive(cls, v: int) -> int:
if v <= 0:
raise ValueError('數量必須大於 0')
return v
@field_validator('unit_price')
@classmethod
def price_must_be_positive(cls, v: Decimal) -> Decimal:
if v <= 0:
raise ValueError('單價必須大於 0')
return v
class CreateOrderRequest(BaseModel):
customer_email: str
items: List[OrderItem]
discount_code: str | None = None
@field_validator('items')
@classmethod
def must_have_items(cls, v: List[OrderItem]) -> List[OrderItem]:
if len(v) == 0:
raise ValueError('訂單至少要有一個品項')
return v
@model_validator(mode='after')
def calculate_total(self) -> 'CreateOrderRequest':
total = sum(item.unit_price * item.quantity for item in self.items)
if total > 1_000_000:
raise ValueError('單筆訂單金額不能超過 100 萬')
return self
# 使用範例
order = CreateOrderRequest(
customer_email="[email protected]",
items=[
{"product_id": "SKU001", "quantity": 2, "unit_price": "299.99"},
{"product_id": "SKU002", "quantity": 1, "unit_price": "599.00"},
]
)
print(order.model_dump_json(indent=2))
這個範例展示了巢狀模型、欄位驗證、跨欄位驗證的綜合運用。在實際的 FastAPI 應用中,你只需要把 CreateOrderRequest 當作路由的參數型別,框架就會自動幫你做驗證。
如果你的 API 還需要處理非同步邏輯,可以搭配 Python asyncio 非同步程式完整教學一起學習。
結語與學習建議
Pydantic V2 讓 Python 的資料驗證變得既強大又高效。回顧一下今天學到的重點:
- BaseModel 是一切的基礎,用型別提示定義欄位就能獲得自動驗證
- field_validator 處理單一欄位的自訂邏輯
- model_validator 處理跨欄位的驗證需求
- V1 到 V2 的遷移有明確的對應規則,搭配工具可以半自動完成
- Rust 核心帶來了數十倍的效能提升
我的建議是,先在小專案中嘗試 V2 的語法,熟悉之後再逐步遷移既有的 V1 程式碼。Pydantic 已經是現代 Python 開發的必備技能,學好它會讓你在處理 API 開發和資料處理時事半功倍。
繼續閱讀
Python FastAPI WebSocket 即時通訊開發教學:從零打造即時聊天室與通知系統
相關文章
你可能也喜歡
探索其他領域的精選好文