> ## Content Index
> Fetch the complete content index at: https://blog.vercanti.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Pydantic 完全指南
- URL: https://blog.vercanti.com/pydantic-wan-quan-zhi-nan/
- Published: 2026-08-28T14:34:44.000Z
- Updated: 2026-08-28T14:57:12.000Z
- Description: Pydantic 是 Python 中最流行的数据验证库，基于 Python 类型注解（Type Hints）实现运行时数据验证、序列化和文档生成。 主要特点： 本文档基于 Pydantic v2。 Pydantic 会尽量将传入数据强制转换为声明的类型： Field() 用于对字段添加更细粒度的约束、默认值、描述等。 适合需要跨字段联合验证的场景。 当模型本身就是一个列表、字典等类型时使用。 FastAPI 深度集成 Pydantic，路由参数、请求体、响应体均使用 Pydantic 模型： .env 文件示例： 验证器是同步的，且会在每次构建模型时调
- Author: yellowdog
- Tags: Python, 框架与库

> 官方文档：<https://docs.pydantic.dev/latest/>  
> 最后更新：2026-03-28

---

## 1\. 基础概念

### Pydantic 是什么

Pydantic 是 Python 中最流行的数据验证库，基于 Python 类型注解（Type Hints）实现运行时数据验证、序列化和文档生成。

主要特点：

- 利用标准 Python 类型注解，无额外学习成本
- 性能极高（v2 核心用 Rust 重写，比 v1 快 5-50 倍）
- 自动类型转换（如字符串 `"123"` 自动转为 `int`）
- 丰富的内置验证器和自定义验证器支持
- 与 FastAPI、SQLAlchemy、Django 等深度集成

### v1 与 v2 的区别

| 特性     | v1              | v2                                  |
| ------ | --------------- | ----------------------------------- |
| 核心实现   | 纯 Python        | Rust（pydantic-core）                 |
| 性能     | 基准              | 快 5-50 倍                            |
| 验证器装饰器 | @validator      | @field\_validator                   |
| 模型配置   | class Config    | model\_config = ConfigDict(...)     |
| 序列化    | dict() / json() | model\_dump() / model\_dump\_json() |
| 私有字段   | PrivateAttr     | PrivateAttr（保留）                     |

本文档基于 **Pydantic v2**。

### 安装

```bash
pip install pydantic

# 可选依赖
pip install pydantic[email]   # 邮箱验证支持
pip install pydantic[dotenv]  # .env 文件支持（通过 pydantic-settings）
pip install pydantic-settings  # 配置管理扩展

```

---

## 2\. 基础使用

### 定义 BaseModel

```python
from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str
    age: int
    email: str | None = None  # 可选字段，默认 None

user = User(id=1, name="Alice", age=25)
print(user.id)    # 1
print(user.name)  # Alice
print(user.email) # None

```

### 自动类型转换

Pydantic 会尽量将传入数据强制转换为声明的类型：

```python
user = User(id="1", name="Alice", age="25")  # 字符串自动转为 int
print(user.id)   # 1 (int)
print(user.age)  # 25 (int)

```

### 验证失败

```python
from pydantic import ValidationError

try:
    User(id="abc", name="Alice", age=25)
except ValidationError as e:
    print(e)
    # 1 validation error for User
    # id
    #   Input should be a valid integer, unable to parse string as an integer [...]

```

### 常用字段类型

```python
from datetime import datetime
from decimal import Decimal
from pydantic import BaseModel

class Product(BaseModel):
    id: int
    name: str
    price: Decimal
    tags: list[str] = []
    metadata: dict[str, str] = {}
    created_at: datetime
    is_active: bool = True
    score: float | None = None

```

---

## 3\. Field() — 字段约束

`Field()` 用于对字段添加更细粒度的约束、默认值、描述等。

### 常用参数

| 参数                   | 类型       | 说明                     |
| -------------------- | -------- | ---------------------- |
| default              | Any      | 字段默认值                  |
| default\_factory     | Callable | 动态默认值工厂函数              |
| title                | str      | 字段标题（用于文档）             |
| description          | str      | 字段描述（用于文档/Schema）      |
| gt                   | float    | 大于（greater than）       |
| ge                   | float    | 大于等于（greater or equal） |
| lt                   | float    | 小于（less than）          |
| le                   | float    | 小于等于（less or equal）    |
| min\_length          | int      | 字符串/列表最小长度             |
| max\_length          | int      | 字符串/列表最大长度             |
| pattern              | str      | 字符串正则表达式约束             |
| examples             | list     | 示例值（用于 Schema）         |
| alias                | str      | 字段别名（用于输入解析）           |
| serialization\_alias | str      | 序列化时使用的别名              |
| exclude              | bool     | 序列化时排除此字段              |
| frozen               | bool     | 字段是否不可修改               |

### 示例

```python
from pydantic import BaseModel, Field
from datetime import datetime

class Article(BaseModel):
    id: int = Field(gt=0, description="文章 ID，必须大于 0")
    title: str = Field(min_length=1, max_length=200, description="文章标题")
    content: str = Field(min_length=10)
    view_count: int = Field(default=0, ge=0)
    tags: list[str] = Field(default_factory=list, max_length=10)
    created_at: datetime = Field(default_factory=datetime.now)
    author_id: int = Field(alias="authorId")  # 接收 JSON 中的 authorId

# 使用别名传入
article = Article(
    id=1,
    title="Python 教程",
    content="这是一篇详细的教程内容",
    authorId=42
)
print(article.author_id)  # 42

```

### 正则约束

```python
from pydantic import BaseModel, Field

class User(BaseModel):
    username: str = Field(pattern=r"^[a-zA-Z0-9_]{3,20}$")
    phone: str = Field(pattern=r"^1[3-9]\d{9}$")

```

---

## 4\. 常用内置类型

### 字符串类型

```python
from pydantic import BaseModel
from pydantic.networks import EmailStr, AnyUrl, HttpUrl, AnyHttpUrl

class Contact(BaseModel):
    email: EmailStr          # 验证邮箱格式，需安装 email-validator
    website: HttpUrl         # 验证 HTTP/HTTPS URL
    any_url: AnyUrl          # 任意 URL

```

### UUID 和 Path

```python
from pathlib import Path
from uuid import UUID
from pydantic import BaseModel

class Resource(BaseModel):
    id: UUID
    file_path: Path

```

### Annotated 类型约束（推荐写法）

```python
from typing import Annotated
from pydantic import BaseModel, Field

# 用 Annotated 复用约束
PositiveInt = Annotated[int, Field(gt=0)]
ShortStr = Annotated[str, Field(max_length=50)]

class Product(BaseModel):
    id: PositiveInt
    name: ShortStr
    stock: PositiveInt

```

### Literal 枚举约束

```python
from typing import Literal
from pydantic import BaseModel

class Order(BaseModel):
    status: Literal["pending", "paid", "shipped", "completed", "cancelled"]
    payment_method: Literal["alipay", "wechat", "card"]

```

---

## 5\. 验证器

### field\_validator — 字段验证器

```python
from pydantic import BaseModel, field_validator

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

    @field_validator("name")
    @classmethod
    def name_must_not_be_empty(cls, v: str) -> str:
        if not v.strip():
            raise ValueError("姓名不能为空白字符串")
        return v.strip()

    @field_validator("age")
    @classmethod
    def age_must_be_adult(cls, v: int) -> int:
        if v < 18:
            raise ValueError("用户必须年满 18 岁")
        return v

    @field_validator("email")
    @classmethod
    def email_to_lowercase(cls, v: str) -> str:
        return v.lower()

```

### 多字段共用同一个验证器

```python
from pydantic import BaseModel, field_validator

class Address(BaseModel):
    city: str
    province: str
    country: str

    @field_validator("city", "province", "country")
    @classmethod
    def strip_whitespace(cls, v: str) -> str:
        return v.strip()

```

### model\_validator — 模型级验证器

适合需要跨字段联合验证的场景。

```python
from pydantic import BaseModel, model_validator
from typing import Self

class DateRange(BaseModel):
    start_date: str
    end_date: str

    @model_validator(mode="after")
    def check_date_order(self) -> Self:
        if self.start_date > self.end_date:
            raise ValueError("start_date 不能晚于 end_date")
        return self

class PasswordForm(BaseModel):
    password: str
    confirm_password: str

    @model_validator(mode="after")
    def passwords_match(self) -> Self:
        if self.password != self.confirm_password:
            raise ValueError("两次密码不一致")
        return self

```

### mode="before" 与 mode="after"

```python
from pydantic import BaseModel, field_validator

class Product(BaseModel):
    price: float

    # before: 在类型转换之前运行，接收原始输入
    @field_validator("price", mode="before")
    @classmethod
    def parse_price_string(cls, v):
        if isinstance(v, str):
            v = v.replace("¥", "").replace(",", "").strip()
        return v

    # after: 在类型转换之后运行，接收已转换的值（默认）
    @field_validator("price", mode="after")
    @classmethod
    def price_must_be_positive(cls, v: float) -> float:
        if v <= 0:
            raise ValueError("价格必须大于 0")
        return v

```

### field\_serializer — 序列化钩子

```python
from datetime import datetime
from pydantic import BaseModel, field_serializer

class Event(BaseModel):
    name: str
    created_at: datetime

    @field_serializer("created_at")
    def serialize_datetime(self, v: datetime) -> str:
        return v.strftime("%Y-%m-%d %H:%M:%S")

event = Event(name="会议", created_at=datetime.now())
print(event.model_dump())
# {'name': '会议', 'created_at': '2026-03-28 10:00:00'}

```

---

## 6\. 模型配置 ConfigDict

```python
from pydantic import BaseModel, ConfigDict

class User(BaseModel):
    model_config = ConfigDict(
        str_strip_whitespace=True,   # 自动去除字符串首尾空格
        str_to_lower=False,          # 字符串转小写
        validate_default=True,       # 对默认值也执行验证
        validate_assignment=True,    # 赋值时也触发验证
        frozen=False,                # True 则模型不可变（字段不可修改）
        populate_by_name=True,       # 允许同时使用字段名和别名
        extra="forbid",              # 禁止额外字段（"allow"/"ignore"/"forbid"）
        use_enum_values=True,        # 存储枚举的值而非枚举对象
        arbitrary_types_allowed=True,# 允许非标准类型
    )

    id: int
    name: str

```

### extra 字段策略

```python
from pydantic import BaseModel, ConfigDict

class StrictModel(BaseModel):
    model_config = ConfigDict(extra="forbid")
    name: str

# 传入多余字段会报错
try:
    StrictModel(name="Alice", unknown_field="value")
except Exception as e:
    print(e)  # Extra inputs are not permitted

class FlexModel(BaseModel):
    model_config = ConfigDict(extra="allow")
    name: str

m = FlexModel(name="Alice", extra_data="value")
print(m.model_extra)  # {'extra_data': 'value'}

```

---

## 7\. 序列化与反序列化

### model\_dump() — 转为字典

```python
from pydantic import BaseModel, Field

class User(BaseModel):
    id: int
    name: str
    password: str = Field(exclude=True)  # 序列化时排除
    email: str | None = None

user = User(id=1, name="Alice", password="secret", email="a@b.com")

user.model_dump()
# {'id': 1, 'name': 'Alice', 'email': 'a@b.com'}

user.model_dump(exclude={"email"})
# {'id': 1, 'name': 'Alice'}

user.model_dump(include={"id", "name"})
# {'id': 1, 'name': 'Alice'}

user.model_dump(exclude_none=True)
# 排除所有值为 None 的字段

user.model_dump(exclude_unset=True)
# 只输出显式设置过的字段（默认值不算）

user.model_dump(by_alias=True)
# 使用 alias 作为 key

```

### model\_dump\_json() — 转为 JSON 字符串

```python
json_str = user.model_dump_json()
json_str = user.model_dump_json(indent=2)
json_str = user.model_dump_json(exclude_none=True)

```

### model\_validate() — 从字典创建

```python
data = {"id": 1, "name": "Alice", "password": "xxx"}
user = User.model_validate(data)

```

### model\_validate\_json() — 从 JSON 字符串创建

```python
json_str = '{"id": 1, "name": "Alice", "password": "xxx"}'
user = User.model_validate_json(json_str)

```

### model\_json\_schema() — 生成 JSON Schema

```python
schema = User.model_json_schema()
import json
print(json.dumps(schema, indent=2, ensure_ascii=False))

```

---

## 8\. 嵌套模型

### 基本嵌套

```python
from pydantic import BaseModel

class Address(BaseModel):
    street: str
    city: str
    country: str = "China"

class User(BaseModel):
    id: int
    name: str
    address: Address

user = User(
    id=1,
    name="Alice",
    address={"street": "中关村大街1号", "city": "北京"}  # 自动转换为 Address
)
print(user.address.city)  # 北京

```

### 列表嵌套

```python
class Tag(BaseModel):
    id: int
    name: str

class Article(BaseModel):
    title: str
    tags: list[Tag] = []

article = Article(
    title="Python 入门",
    tags=[{"id": 1, "name": "Python"}, {"id": 2, "name": "编程"}]
)

```

### 递归模型

```python
from __future__ import annotations
from pydantic import BaseModel

class Category(BaseModel):
    id: int
    name: str
    children: list[Category] = []

Category.model_rebuild()  # v2 中递归模型需调用此方法

```

---

## 9\. 高级用法

### 泛型模型

```python
from typing import Generic, TypeVar
from pydantic import BaseModel

T = TypeVar("T")

class Response(BaseModel, Generic[T]):
    code: int = 200
    message: str = "success"
    data: T | None = None

class UserInfo(BaseModel):
    id: int
    name: str

# 使用
resp: Response[UserInfo] = Response(data=UserInfo(id=1, name="Alice"))
resp: Response[list[UserInfo]] = Response(data=[UserInfo(id=1, name="Alice")])
resp: Response[None] = Response(code=404, message="not found")

```

### 动态创建模型

```python
from pydantic import create_model

# 动态定义字段
DynamicModel = create_model(
    "DynamicModel",
    name=(str, ...),        # (类型, 默认值)，... 表示必填
    age=(int, 18),
    email=(str | None, None),
)

obj = DynamicModel(name="Alice", age=25)

```

### 模型继承

```python
from pydantic import BaseModel

class BaseUser(BaseModel):
    id: int
    name: str

class CreateUser(BaseUser):
    id: int = None  # 创建时可以不传 id
    password: str

class UpdateUser(BaseModel):
    name: str | None = None
    password: str | None = None

class UserInDB(BaseUser):
    hashed_password: str
    is_active: bool = True

```

### 使用 Python 枚举

```python
from enum import Enum
from pydantic import BaseModel, ConfigDict

class StatusEnum(str, Enum):
    ACTIVE = "active"
    INACTIVE = "inactive"
    BANNED = "banned"

class User(BaseModel):
    model_config = ConfigDict(use_enum_values=True)
    name: str
    status: StatusEnum = StatusEnum.ACTIVE

user = User(name="Alice", status="inactive")
print(user.status)       # inactive（use_enum_values=True 存储值而非枚举对象）
print(type(user.status)) # <class 'str'>

```

### 私有属性

```python
from pydantic import BaseModel, PrivateAttr

class Session(BaseModel):
    user_id: int
    _token: str = PrivateAttr(default="")         # 私有属性，不参与验证和序列化
    _cache: dict = PrivateAttr(default_factory=dict)

    def set_token(self, token: str):
        self._token = token

s = Session(user_id=1)
s.set_token("abc123")
print(s.model_dump())   # {'user_id': 1}，不包含 _token

```

### model\_copy() — 复制模型并修改

```python
user = User(id=1, name="Alice", age=25)

# 浅复制并修改字段
updated = user.model_copy(update={"name": "Bob", "age": 30})
print(updated.name)  # Bob
print(user.name)     # Alice（原对象不变）

```

### Computed Fields — 计算字段

```python
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

    @computed_field
    @property
    def perimeter(self) -> float:
        return 2 * (self.width + self.height)

r = Rectangle(width=3.0, height=4.0)
print(r.area)       # 12.0
print(r.model_dump()) # {'width': 3.0, 'height': 4.0, 'area': 12.0, 'perimeter': 14.0}

```

### 严格模式

```python
from pydantic import BaseModel, ConfigDict

class StrictUser(BaseModel):
    model_config = ConfigDict(strict=True)
    id: int
    name: str

# 严格模式不允许自动类型转换
try:
    StrictUser(id="1", name="Alice")  # 报错：id 必须是 int，不接受 str
except Exception as e:
    print(e)

```

### model\_fields 和运行时内省

```python
from pydantic import BaseModel, Field

class User(BaseModel):
    id: int
    name: str = Field(description="用户名")

# 获取所有字段信息
for field_name, field_info in User.model_fields.items():
    print(f"{field_name}: {field_info.annotation}, required={field_info.is_required()}")

```

---

## 10\. RootModel — 根类型模型

当模型本身就是一个列表、字典等类型时使用。

```python
from pydantic import RootModel

class Tags(RootModel[list[str]]):
    pass

tags = Tags.model_validate(["python", "pydantic", "fastapi"])
print(tags.root)          # ['python', 'pydantic', 'fastapi']
print(tags.model_dump())  # ['python', 'pydantic', 'fastapi']

class UserMap(RootModel[dict[str, int]]):
    pass

scores = UserMap.model_validate({"Alice": 90, "Bob": 85})
print(scores.root)  # {'Alice': 90, 'Bob': 85}

```

---

## 11\. 与常用库的配合

### 与 FastAPI 配合

FastAPI 深度集成 Pydantic，路由参数、请求体、响应体均使用 Pydantic 模型：

```python
from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI()

class UserCreate(BaseModel):
    name: str = Field(min_length=1, max_length=50)
    email: str
    age: int = Field(ge=0, le=150)

class UserResponse(BaseModel):
    id: int
    name: str
    email: str

@app.post("/users", response_model=UserResponse)
async def create_user(user: UserCreate):
    # FastAPI 自动验证请求体，验证失败返回 422
    # response_model 自动过滤响应字段（如密码等敏感字段）
    return UserResponse(id=1, name=user.name, email=user.email)

```

### 与 SQLAlchemy 配合（ORM 模式）

```python
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import DeclarativeBase
from pydantic import BaseModel, ConfigDict

class Base(DeclarativeBase):
    pass

class UserORM(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    name = Column(String)
    email = Column(String)

# Pydantic 模型开启 ORM 模式，可直接从 ORM 对象创建
class UserSchema(BaseModel):
    model_config = ConfigDict(from_attributes=True)
    id: int
    name: str
    email: str

# 使用
orm_obj = UserORM(id=1, name="Alice", email="a@b.com")
user = UserSchema.model_validate(orm_obj)  # 从 ORM 对象创建
print(user.model_dump())

```

### 与 pydantic-settings 配合（配置管理）

```python
from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
    model_config = SettingsConfigDict(
        env_file=".env",          # 从 .env 文件读取
        env_file_encoding="utf-8",
        case_sensitive=False,     # 环境变量大小写不敏感
        extra="ignore",
    )

    app_name: str = "MyApp"
    debug: bool = False
    database_url: str
    secret_key: str
    allowed_hosts: list[str] = ["localhost"]
    max_connections: int = 10

# 单例模式（推荐）
from functools import lru_cache

@lru_cache
def get_settings() -> Settings:
    return Settings()

settings = get_settings()
print(settings.database_url)

```

`.env` 文件示例：

```
DATABASE_URL=postgresql://user:pass@localhost/db
SECRET_KEY=your-secret-key
DEBUG=true
ALLOWED_HOSTS=["localhost", "example.com"]

```

### 与 Tortoise ORM 配合

```python
from tortoise.models import Model
from tortoise import fields
from pydantic import BaseModel, ConfigDict

class UserModel(Model):
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=100)
    email = fields.CharField(max_length=255)

    class Meta:
        table = "users"

# 手动定义 Pydantic Schema
class UserSchema(BaseModel):
    model_config = ConfigDict(from_attributes=True)
    id: int
    name: str
    email: str

# Tortoise 也有内置的 pydantic_model_creator
from tortoise.contrib.pydantic import pydantic_model_creator

UserPydantic = pydantic_model_creator(UserModel, name="User")
UserPydanticIn = pydantic_model_creator(UserModel, name="UserIn", exclude_readonly=True)

```

### 与 Redis / 缓存配合

```python
import json
import redis
from pydantic import BaseModel

class UserCache(BaseModel):
    id: int
    name: str
    permissions: list[str] = []

r = redis.Redis()

def cache_user(user: UserCache):
    r.setex(f"user:{user.id}", 3600, user.model_dump_json())

def get_cached_user(user_id: int) -> UserCache | None:
    data = r.get(f"user:{user_id}")
    if data:
        return UserCache.model_validate_json(data)
    return None

```

---

## 12\. 常用代码段

### 通用 API 响应体

```python
from typing import Generic, TypeVar
from pydantic import BaseModel

T = TypeVar("T")

class ApiResponse(BaseModel, Generic[T]):
    code: int = 200
    message: str = "success"
    data: T | None = None

    @classmethod
    def ok(cls, data: T = None, message: str = "success") -> "ApiResponse[T]":
        return cls(code=200, message=message, data=data)

    @classmethod
    def error(cls, code: int = 400, message: str = "error") -> "ApiResponse[None]":
        return cls(code=code, message=message, data=None)

# 使用
resp = ApiResponse.ok(data={"id": 1, "name": "Alice"})
resp = ApiResponse.error(code=404, message="用户不存在")

```

### 分页响应

```python
from typing import Generic, TypeVar
from pydantic import BaseModel

T = TypeVar("T")

class PageResult(BaseModel, Generic[T]):
    items: list[T]
    total: int
    page: int
    page_size: int

    @property
    def total_pages(self) -> int:
        return (self.total + self.page_size - 1) // self.page_size

    @property
    def has_next(self) -> bool:
        return self.page < self.total_pages

```

### 时间戳字段自动处理

```python
from datetime import datetime
from pydantic import BaseModel, Field, field_serializer

class TimestampMixin(BaseModel):
    created_at: datetime = Field(default_factory=datetime.now)
    updated_at: datetime = Field(default_factory=datetime.now)

    @field_serializer("created_at", "updated_at")
    def serialize_dt(self, v: datetime) -> str:
        return v.strftime("%Y-%m-%d %H:%M:%S")

class Article(TimestampMixin):
    title: str
    content: str

```

### 密码哈希（与 passlib 配合）

```python
from pydantic import BaseModel, field_validator
from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"])

class UserCreate(BaseModel):
    username: str
    password: str

    @field_validator("password")
    @classmethod
    def hash_password(cls, v: str) -> str:
        return pwd_context.hash(v)

```

### 从环境变量读取数据库配置

```python
from pydantic_settings import BaseSettings

class DatabaseSettings(BaseSettings):
    host: str = "localhost"
    port: int = 5432
    user: str = "postgres"
    password: str
    name: str

    @property
    def url(self) -> str:
        return f"postgresql+asyncpg://{self.user}:{self.password}@{self.host}:{self.port}/{self.name}"

    class Config:
        env_prefix = "DB_"  # 环境变量前缀：DB_HOST, DB_PORT...

```

### 将 ValidationError 转为可读信息

```python
from pydantic import ValidationError

def format_validation_error(e: ValidationError) -> dict:
    errors = {}
    for error in e.errors():
        field = " -> ".join(str(loc) for loc in error["loc"])
        errors[field] = error["msg"]
    return errors

try:
    User(id="abc", name="")
except ValidationError as e:
    print(format_validation_error(e))
    # {'id': 'Input should be a valid integer...', 'name': '...'}

```

### 递归 dict 转换（深度序列化）

```python
from pydantic import BaseModel

class Inner(BaseModel):
    value: int

class Outer(BaseModel):
    name: str
    inner: Inner

obj = Outer(name="test", inner=Inner(value=42))
# 深度转换为原生 dict（嵌套也是 dict）
data = obj.model_dump()
print(type(data["inner"]))  # dict

```

---

## 13\. 最佳实践

### 分离 Create / Update / Response Schema

```python
from pydantic import BaseModel, Field

# 创建时的 Schema（id 由服务器生成，不需要传入）
class UserCreate(BaseModel):
    name: str = Field(min_length=1, max_length=100)
    email: str
    password: str = Field(min_length=8)

# 更新时全部字段可选
class UserUpdate(BaseModel):
    name: str | None = Field(default=None, min_length=1, max_length=100)
    email: str | None = None

# 返回给客户端的 Schema（不含密码）
class UserResponse(BaseModel):
    id: int
    name: str
    email: str
    is_active: bool

```

### 使用 Annotated 复用类型约束

```python
from typing import Annotated
from pydantic import Field

# 定义复用类型
PositiveInt = Annotated[int, Field(gt=0)]
NonEmptyStr = Annotated[str, Field(min_length=1)]
PhoneStr = Annotated[str, Field(pattern=r"^1[3-9]\d{9}$")]

```

### 善用 exclude\_unset 实现 PATCH 更新

```python
from pydantic import BaseModel

class UserUpdate(BaseModel):
    name: str | None = None
    email: str | None = None
    age: int | None = None

# PATCH 请求只传部分字段
payload = UserUpdate(name="Bob")
# exclude_unset=True 只返回用户实际传入的字段
update_data = payload.model_dump(exclude_unset=True)
print(update_data)  # {'name': 'Bob'}（不包含 email 和 age）

# 然后用这些字段去更新数据库
# db_user.update(**update_data)

```

### 不要在 validator 中做 IO 操作

验证器是同步的，且会在每次构建模型时调用，不应在其中执行数据库查询、网络请求等 IO 操作。把这类逻辑放到 service 层：

```python
# 不推荐
class UserCreate(BaseModel):
    username: str

    @field_validator("username")
    @classmethod
    def check_username_unique(cls, v):
        # 不要在这里查数据库
        if db.query(User).filter_by(username=v).first():
            raise ValueError("用户名已存在")
        return v

# 推荐：在 service 层做
async def create_user(data: UserCreate):
    if await User.exists(username=data.username):
        raise HTTPException(400, "用户名已存在")
    return await User.create(**data.model_dump())

```

### 使用 model\_config 统一配置

```python
from pydantic import BaseModel, ConfigDict

class AppBaseModel(BaseModel):
    """项目内所有 Schema 的基类，统一配置。"""
    model_config = ConfigDict(
        str_strip_whitespace=True,
        validate_default=True,
        populate_by_name=True,
    )

class User(AppBaseModel):
    name: str
    email: str

```

### 避免过度嵌套

层级过深的嵌套模型难以维护，建议超过 3 层时拆分为独立 Schema，并在文档中用 `双链` 标注关联关系。

---

## 14\. 踩坑与注意事项

### list / dict 默认值必须用 default\_factory

```python
from pydantic import BaseModel, Field

# 错误写法（共享同一个列表对象）
class Bad(BaseModel):
    tags: list[str] = []  # Pydantic v2 实际上会处理这个，但仍推荐下面的写法

# 推荐写法
class Good(BaseModel):
    tags: list[str] = Field(default_factory=list)
    metadata: dict = Field(default_factory=dict)

```

### alias 与字段名同时使用需开启 populate\_by\_name

```python
from pydantic import BaseModel, ConfigDict, Field

class User(BaseModel):
    model_config = ConfigDict(populate_by_name=True)
    user_id: int = Field(alias="userId")

# 两种方式都可以
User(userId=1)   # 使用 alias
User(user_id=1)  # 使用字段名

```

### Optional 与 required 的区别

```python
from pydantic import BaseModel

class User(BaseModel):
    # 必填字段（没有默认值）
    name: str
    # 可选字段（有默认值 None，可以不传）
    nickname: str | None = None
    # 可选字段（必须显式传入，但值可以是 None）
    bio: str | None  # 这个字段是 required 的，必须传，但可以传 None

```

### v2 中 @validator 已废弃

```python
# v1 写法（v2 中仍可用但会警告）
from pydantic import validator

class OldStyle(BaseModel):
    name: str

    @validator("name")
    def check_name(cls, v):
        return v

# v2 正确写法
from pydantic import field_validator

class NewStyle(BaseModel):
    name: str

    @field_validator("name")
    @classmethod
    def check_name(cls, v: str) -> str:
        return v

```

### model\_dump() 嵌套模型默认展开为 dict

```python
class Inner(BaseModel):
    value: int

class Outer(BaseModel):
    inner: Inner

obj = Outer(inner=Inner(value=1))
data = obj.model_dump()
print(data)           # {'inner': {'value': 1}}
print(type(data["inner"]))  # dict，不是 Inner 对象

```

### frozen=True 时字段不可修改

```python
from pydantic import BaseModel, ConfigDict

class ImmutableUser(BaseModel):
    model_config = ConfigDict(frozen=True)
    id: int
    name: str

user = ImmutableUser(id=1, name="Alice")
user.name = "Bob"  # 报错：Instance is frozen

```

---

## 最佳实践

**用 `model_validator(mode='before')` 做跨字段预处理**：在字段验证之前统一转换格式（如日期字符串标准化），避免每个字段的 `field_validator` 重复处理相同逻辑。

**`model_dump(exclude_none=True)` 过滤空值**：构建 API 请求体或 PATCH 操作时，用 `exclude_none=True` 或 `exclude_unset=True` 只输出用户明确设置的字段，避免覆盖服务端默认值。

**用 `TypeAdapter` 验证非模型类型**：需要验证 `list[User]`、`dict[str, int]` 等类型时，用 `TypeAdapter` 而非创建包装模型：

```python
from pydantic import TypeAdapter
adapter = TypeAdapter(list[int])
adapter.validate_python([1, "2", 3.0])  # [1, 2, 3]

```

**为 API 响应单独定义 Public Schema**：数据库模型包含密码哈希等敏感字段，定义 `UserPublic(BaseModel)` 只暴露安全字段，作为 `response_model`。

**`model_config = ConfigDict(from_attributes=True)` 支持 ORM 对象输入**：与 SQLAlchemy/Tortoise-ORM 集成时必须设置，让 Pydantic 能从 ORM model 的属性中读取数据。

---

## 常见陷阱

### 陷阱：`model_dump()` 嵌套模型仍是 Pydantic 对象

**现象：** `user.model_dump()['address']` 是 dict，但 `user.dict()['address']` 也是 dict——这没问题。问题是直接访问 `user.address` 是 `Address` 对象，JSON 序列化时报错。  
**原因：** 直接 `json.dumps(user.model_dump())` 会因嵌套对象的日期、UUID 等类型报序列化错误。  
**解决：** 用 `user.model_dump(mode='json')` 或 `user.model_dump_json()` 而非 `json.dumps(user.model_dump())`。

### 陷阱：`validator`（v1 写法）在 v2 中失效

**现象：** 迁移到 Pydantic v2 后，用 `@validator('field')` 装饰器的验证方法静默不执行。  
**原因：** Pydantic v2 不向后兼容 v1 的 `@validator`，需改用 `@field_validator`，且签名有变化（类方法，第一个参数为值）。  
**解决：** 用 `from pydantic import field_validator` 替换，或临时用 `model_config = ConfigDict(arbitrary_types_allowed=True)` 结合 `v1_compat` 模式。

### 陷阱：`Optional[str]` 不等于 `str | None`（Pydantic v2）

**现象：** v2 中 `Optional[str]` 字段在未传时默认值是 `None` 不需要显式赋值，但某些场景下字段值为 `None` 时验证报错。  
**原因：** Pydantic v2 更严格区分"有值但为 None"和"未提供"，`Optional[str]` 默认 `default` 仍需显式设置 `= None`。  
**解决：** 始终显式声明默认值：`name: str | None = None`，不依赖隐式默认。

---

## 参见

- [FastAPI完全指南](https://blog.vercanti.com/fastapi-wan-quan-zhi-nan/)
- [SQLAlchemy完全指南](https://blog.vercanti.com/sqlalchemy-wan-quan-zhi-nan/)
- [SQLModel完全指南](https://blog.vercanti.com/sqlmodel-wan-quan-zhi-nan/)
- [数据类型](https://blog.vercanti.com/python-nei-zhi-shu-ju-lei-xing-wan-quan-can-kao/)