02-1. Pydantic 基礎模型

⏱️ 閱讀時間: 12 分鐘 🎯 難度: ⭐⭐ (基礎)


🤔 一句話解釋

Pydantic 是 Python 資料驗證的瑞士刀,讓你用 Type Hints 定義資料結構,自動驗證和轉換資料。


🎯 為什麼需要 Pydantic?

沒有 Pydantic 的痛苦

# ❌ 傳統方式:手動驗證
def create_user(data: dict):
    # 檢查必填欄位
    if "email" not in data:
        raise ValueError("email is required")
    if "age" not in data:
        raise ValueError("age is required")

    # 檢查型別
    if not isinstance(data["email"], str):
        raise ValueError("email must be string")
    if not isinstance(data["age"], int):
        raise ValueError("age must be integer")

    # 檢查格式
    if "@" not in data["email"]:
        raise ValueError("invalid email format")
    if data["age"] < 0 or data["age"] > 150:
        raise ValueError("age must be between 0 and 150")

    # 終於可以使用了...
    return data

有 Pydantic 的優雅

# ✅ Pydantic 方式:宣告式驗證
from pydantic import BaseModel, EmailStr, Field

class User(BaseModel):
    email: EmailStr
    age: int = Field(ge=0, le=150)

# 使用
user = User(email="john@example.com", age=25)  # ✅ 成功
user = User(email="invalid", age=25)            # ❌ 自動拋錯

📦 Pydantic v2 基礎

安裝

pip install pydantic
# FastAPI 已經包含 pydantic,不用另外裝

檢查版本

import pydantic
print(pydantic.__version__)  # 2.x.x

🏗️ 定義 BaseModel

基本結構

from pydantic import BaseModel

class User(BaseModel):
    name: str
    age: int
    email: str

建立實例

# 方法 1:關鍵字參數
user = User(name="John", age=25, email="john@example.com")

# 方法 2:從字典
data = {"name": "John", "age": 25, "email": "john@example.com"}
user = User(**data)

# 方法 3:使用 model_validate(推薦)
user = User.model_validate(data)

# 方法 4:從 JSON 字串
json_str = '{"name": "John", "age": 25, "email": "john@example.com"}'
user = User.model_validate_json(json_str)

存取屬性

user = User(name="John", age=25, email="john@example.com")

# 存取屬性
print(user.name)    # John
print(user.age)     # 25

# 轉換為字典
print(user.model_dump())
# {'name': 'John', 'age': 25, 'email': 'john@example.com'}

# 轉換為 JSON
print(user.model_dump_json())
# '{"name":"John","age":25,"email":"john@example.com"}'

📊 支援的型別

基本型別

from pydantic import BaseModel
from typing import Optional, List, Dict, Set, Tuple, Any
from datetime import datetime, date, time, timedelta
from decimal import Decimal
from uuid import UUID
from pathlib import Path

class AllTypes(BaseModel):
    # 基本型別
    string_field: str
    int_field: int
    float_field: float
    bool_field: bool
    bytes_field: bytes

    # 日期時間
    datetime_field: datetime
    date_field: date
    time_field: time
    duration: timedelta

    # 其他
    decimal_field: Decimal
    uuid_field: UUID
    path_field: Path

    # 容器型別
    list_field: List[int]
    dict_field: Dict[str, int]
    set_field: Set[str]
    tuple_field: Tuple[int, str, float]

    # 可選
    optional_field: Optional[str] = None

    # 任意型別
    any_field: Any = None

自動型別轉換

from pydantic import BaseModel

class Demo(BaseModel):
    count: int
    price: float
    active: bool

# Pydantic 會自動轉換相容的型別
demo = Demo(count="42", price="99.9", active="true")
print(demo)
# count=42 price=99.9 active=True

# 注意:字串 "42" 自動轉成 int 42

嚴格模式

from pydantic import BaseModel, ConfigDict

class StrictDemo(BaseModel):
    model_config = ConfigDict(strict=True)

    count: int
    name: str

# 嚴格模式下不會自動轉換
StrictDemo(count="42", name="John")  # ❌ ValidationError
StrictDemo(count=42, name="John")    # ✅ OK

🔧 預設值

基本預設值

from pydantic import BaseModel
from typing import Optional, List

class User(BaseModel):
    name: str                          # 必填
    age: int = 0                       # 有預設值,選填
    email: Optional[str] = None        # 可為 None
    tags: List[str] = []               # 空列表

# ⚠️ 注意:可變預設值的陷阱
class Wrong(BaseModel):
    items: List[str] = []  # 每次都是新的列表,Pydantic 處理了這個問題

使用 Field 設定預設值

from pydantic import BaseModel, Field

class Product(BaseModel):
    name: str
    price: float = Field(default=0.0)
    quantity: int = Field(default=1)

    # 使用工廠函數作為預設值
    tags: List[str] = Field(default_factory=list)
    metadata: Dict[str, Any] = Field(default_factory=dict)

必填欄位

from pydantic import BaseModel, Field

class User(BaseModel):
    # 方法 1:不給預設值
    name: str

    # 方法 2:使用 ... (Ellipsis)
    email: str = Field(...)

    # 方法 3:明確標註(Python 3.10+)
    # from typing import Required
    # age: Required[int]

🏷️ Field 詳解

Field 的作用

from pydantic import BaseModel, Field

class User(BaseModel):
    name: str = Field(
        ...,                           # 必填
        min_length=1,                  # 最小長度
        max_length=50,                 # 最大長度
        title="使用者名稱",             # 標題(文件用)
        description="使用者的顯示名稱", # 描述(文件用)
        examples=["John", "Jane"],     # 範例(文件用)
    )

    age: int = Field(
        default=0,
        ge=0,                          # >= 0
        le=150,                        # <= 150
        title="年齡",
        description="使用者的年齡",
    )

    email: str = Field(
        ...,
        pattern=r"^[\w\.-]+@[\w\.-]+\.\w+$",  # 正規表達式
    )

Field 常用參數

參數說明適用型別
default預設值全部
default_factory預設值工廠函數全部
alias欄位別名全部
title標題全部
description描述全部
examples範例全部
gt大於數字
ge大於等於數字
lt小於數字
le小於等於數字
multiple_of倍數數字
min_length最小長度字串/列表
max_length最大長度字串/列表
pattern正規表達式字串
strict嚴格模式全部
frozen不可變全部
deprecated標記為棄用全部

🔄 模型方法

常用方法

from pydantic import BaseModel

class User(BaseModel):
    name: str
    age: int
    email: str

user = User(name="John", age=25, email="john@example.com")

# 轉換為字典
user.model_dump()
# {'name': 'John', 'age': 25, 'email': 'john@example.com'}

# 排除特定欄位
user.model_dump(exclude={"email"})
# {'name': 'John', 'age': 25}

# 只包含特定欄位
user.model_dump(include={"name", "age"})
# {'name': 'John', 'age': 25}

# 排除預設值
user.model_dump(exclude_defaults=True)

# 排除未設定的值
user.model_dump(exclude_unset=True)

# 排除 None 值
user.model_dump(exclude_none=True)

# 轉換為 JSON 字串
user.model_dump_json()
# '{"name":"John","age":25,"email":"john@example.com"}'

# 格式化 JSON
user.model_dump_json(indent=2)

# 複製模型
user_copy = user.model_copy()

# 複製並更新
user_updated = user.model_copy(update={"age": 26})

類別方法

# 從字典建立
User.model_validate({"name": "John", "age": 25, "email": "john@example.com"})

# 從 JSON 建立
User.model_validate_json('{"name": "John", "age": 25, "email": "john@example.com"}')

# 獲取 JSON Schema
User.model_json_schema()

# 獲取欄位資訊
User.model_fields
# {'name': FieldInfo(...), 'age': FieldInfo(...), 'email': FieldInfo(...)}

📝 實戰範例

電商商品模型

from pydantic import BaseModel, Field, ConfigDict
from typing import Optional, List
from datetime import datetime
from decimal import Decimal
from enum import Enum

class Category(str, Enum):
    ELECTRONICS = "electronics"
    CLOTHING = "clothing"
    FOOD = "food"
    BOOKS = "books"

class ProductImage(BaseModel):
    url: str
    alt_text: Optional[str] = None
    is_primary: bool = False

class ProductCreate(BaseModel):
    """建立商品請求"""
    name: str = Field(..., min_length=1, max_length=200)
    description: Optional[str] = Field(None, max_length=5000)
    price: Decimal = Field(..., ge=0, decimal_places=2)
    category: Category
    tags: List[str] = Field(default_factory=list, max_length=10)
    images: List[ProductImage] = Field(default_factory=list, max_length=20)
    stock: int = Field(default=0, ge=0)
    is_active: bool = True

class ProductResponse(BaseModel):
    """商品回應"""
    id: int
    name: str
    description: Optional[str]
    price: Decimal
    category: Category
    tags: List[str]
    images: List[ProductImage]
    stock: int
    is_active: bool
    created_at: datetime
    updated_at: Optional[datetime] = None

    model_config = ConfigDict(from_attributes=True)

# 使用範例
product = ProductCreate(
    name="iPhone 15 Pro",
    description="最新款 iPhone",
    price=Decimal("35900.00"),
    category=Category.ELECTRONICS,
    tags=["手機", "Apple", "5G"],
    images=[
        ProductImage(url="https://example.com/img1.jpg", is_primary=True),
        ProductImage(url="https://example.com/img2.jpg"),
    ],
    stock=100
)

print(product.model_dump_json(indent=2))

✅ 重點總結

Pydantic 核心概念

概念說明
BaseModel所有模型的基礎類別
Field定義欄位的驗證規則
model_dump()轉換為字典
model_validate()從字典建立實例
model_dump_json()轉換為 JSON

最佳實踐

  1. 使用 Type Hints - 讓 IDE 和 Pydantic 都能理解
  2. 使用 Field - 定義清楚的驗證規則
  3. 區分請求/回應模型 - Create, Update, Response 分開
  4. 使用 Enum - 限制欄位的可能值
  5. 添加文件 - title, description, examples

🎤 面試這樣答

Q: Pydantic 是什麼?有什麼優點?

答案:

Pydantic 是一個 Python 資料驗證和設定管理的函式庫。主要優點:

  1. 宣告式驗證:用 Type Hints 定義資料結構,自動完成驗證
  2. 自動型別轉換:可以把字串 “42” 轉成 int 42
  3. 效能優異:v2 版本使用 Rust 編寫核心,速度很快
  4. IDE 支援:完整的型別提示和自動補全
  5. JSON Schema:自動生成 JSON Schema,整合 FastAPI 文件

🤓 小測驗

  1. 如何讓一個欄位變成必填?

  2. model_dump()model_dump_json() 的差別?

  3. 如何限制數字欄位必須大於 0?


上一篇: 01-7. 專案結構最佳實踐 下一篇: 02-2. 欄位驗證與約束


最後更新:2025-12-17

0%