目錄
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 |
最佳實踐
- 使用 Type Hints - 讓 IDE 和 Pydantic 都能理解
- 使用 Field - 定義清楚的驗證規則
- 區分請求/回應模型 - Create, Update, Response 分開
- 使用 Enum - 限制欄位的可能值
- 添加文件 - title, description, examples
🎤 面試這樣答
Q: Pydantic 是什麼?有什麼優點?
答案:
Pydantic 是一個 Python 資料驗證和設定管理的函式庫。主要優點:
- 宣告式驗證:用 Type Hints 定義資料結構,自動完成驗證
- 自動型別轉換:可以把字串 “42” 轉成 int 42
- 效能優異:v2 版本使用 Rust 編寫核心,速度很快
- IDE 支援:完整的型別提示和自動補全
- JSON Schema:自動生成 JSON Schema,整合 FastAPI 文件
🤓 小測驗
如何讓一個欄位變成必填?
model_dump()和model_dump_json()的差別?如何限制數字欄位必須大於 0?
上一篇: 01-7. 專案結構最佳實踐 下一篇: 02-2. 欄位驗證與約束
最後更新:2025-12-17