自訂驗證器
自訂驗證器(validator)用來檢查型別與 Field 表達不出來的規則,例如「密碼要包含數字」「結束日期必須晚於開始日期」。Pydantic V2 提供兩個主要裝飾器:@field_validator 針對單一欄位,@model_validator 針對整個模型、可以比較多個欄位。本頁也介紹由其他欄位計算而來的 @computed_field。
如何驗證單一欄位?
@field_validator 接收欄位值,驗證通過就回傳(可以順便轉換)值,不通過就丟出 ValueError:
from pydantic import BaseModel, ValidationError, field_validator
class SignUp(BaseModel):
username: str
password: str
@field_validator("username")
@classmethod
def normalize_username(cls, v: str) -> str:
return v.strip().lower() # 轉換:去空白、轉小寫
@field_validator("password")
@classmethod
def password_has_digit(cls, v: str) -> str:
if not any(c.isdigit() for c in v):
raise ValueError("密碼至少要包含一個數字")
return v
print(SignUp(username=" Alice ", password="abc123").username) # alice
try:
SignUp(username="bob", password="abcdef")
except ValidationError as e:
print(e.errors()[0]["msg"]) # Value error, 密碼至少要包含一個數字
重點:
- 驗證器必須是
@classmethod,而且一定要回傳值;忘了return欄位會變成None。 - 丟出
ValueError或AssertionError會被轉成ValidationError;不要丟出ValidationError本身。 - 一個驗證器可以同時套用多個欄位:
@field_validator("first_name", "last_name")。
mode="before" 和 mode="after" 差在哪?
| 模式 | 執行時機 | 拿到的值 | 常見用途 |
|---|---|---|---|
"after"(預設) |
Pydantic 型別驗證之後 | 已經是正確型別 | 檢查商業規則 |
"before" |
型別驗證之前 | 原始輸入,型別不確定 | 預先清理資料,例如把 "a,b,c" 拆成串列 |
from typing import Any
from pydantic import BaseModel, field_validator
class Article(BaseModel):
tags: list[str]
@field_validator("tags", mode="before")
@classmethod
def split_comma_string(cls, v: Any) -> Any:
if isinstance(v, str):
return [t.strip() for t in v.split(",")]
return v
print(Article(tags="python, pydantic").tags) # ['python', 'pydantic']
如何檢查多個欄位之間的關係?
需要同時看多個欄位時,用 @model_validator(mode="after")。它是實例方法,拿到的是已經驗證好的模型物件,最後要 return self:
from datetime import date
from pydantic import BaseModel, ValidationError, model_validator
class Booking(BaseModel):
check_in: date
check_out: date
@model_validator(mode="after")
def check_dates(self) -> "Booking":
if self.check_out <= self.check_in:
raise ValueError("退房日期必須晚於入住日期")
return self
try:
Booking(check_in="2026-10-11", check_out="2026-10-10")
except ValidationError as e:
print(e.errors()[0]["msg"]) # Value error, 退房日期必須晚於入住日期
@model_validator(mode="before") 則是 @classmethod,拿到的是原始輸入(通常是 dict),適合在驗證前整理整份資料的結構。
驗證器裡怎麼讀取其他欄位的值?
@field_validator 可以多接一個 info: ValidationInfo 參數,透過 info.data 讀取已經驗證完成的前面欄位:
from pydantic import BaseModel, ValidationInfo, field_validator
class ChangePassword(BaseModel):
password: str
confirm: str
@field_validator("confirm")
@classmethod
def passwords_match(cls, v: str, info: ValidationInfo) -> str:
if v != info.data.get("password"):
raise ValueError("兩次輸入的密碼不一致")
return v
欄位依宣告順序驗證,所以 info.data 只看得到排在前面的欄位;如果前面的欄位驗證失敗,它也不會出現在 info.data 裡。邏輯更複雜時,改用 @model_validator 比較清楚。
可以用 Annotated 寫可重複使用的驗證器嗎?
可以。AfterValidator、BeforeValidator 把驗證邏輯綁在型別上,適合多個模型共用:
from typing import Annotated
from pydantic import AfterValidator, BaseModel
def must_be_even(v: int) -> int:
if v % 2:
raise ValueError("必須是偶數")
return v
EvenInt = Annotated[int, AfterValidator(must_be_even)]
class Pair(BaseModel):
left: EvenInt
right: EvenInt
print(Pair(left=2, right="4")) # left=2 right=4
由其他欄位計算出來的值要怎麼寫?
用 @computed_field 搭配 @property,計算結果會出現在 model_dump() 與 JSON Schema 中,但不能從輸入設定:
from pydantic import BaseModel, computed_field
class Rectangle(BaseModel):
width: float
height: float
@computed_field
@property
def area(self) -> float:
return self.width * self.height
print(Rectangle(width=3, height=4).model_dump())
# {'width': 3.0, 'height': 4.0, 'area': 12.0}
推薦影音
James Clare:Field Validators
簡述:約 11 分鐘的系列第 2 集,示範 @field_validator 的基本寫法:檢查欄位值、丟出錯誤與回傳轉換後的值,對應本頁第一節。
James Clare:Model Validators
簡述:同系列第 3 集,說明如何用 @model_validator 對整個模型做驗證,對應本頁「如何檢查多個欄位之間的關係」。