跳轉至

建立 2026-10-11 更新 2026-10-11

自訂驗證器

自訂驗證器(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 對整個模型做驗證,對應本頁「如何檢查多個欄位之間的關係」。

相關資料