🦊 Python 数据验证库

Pydantic

用 Python 类型注解驱动的数据验证 & 序列化框架

⚡ 类型驱动 ✅ 自动验证 🔄 序列化/反序列化 🚀 FastAPI 核心依赖
1
Pydantic 是什么?

一句话:Pydantic 是 Python 中最流行的 数据验证与序列化 库,核心思想是用 Python 的类型注解来定义数据结构,并自动完成验证、转换、序列化。

🔍

数据验证(Validation)

传入任意原始数据(dict/JSON/字符串),Pydantic 自动按照你定义的类型进行验证,不合法就抛出详细的错误信息。

🔄

类型强制转换(Coercion)

如果传入 "123" 但你声明了 int,Pydantic 会自动把字符串转成整数,而不是报错。

📦

序列化(Serialization)

把 Pydantic 模型实例转成 dict、JSON 字符串等格式,可以自定义字段别名、排除字段、格式化输出。

📋

Schema 生成

自动生成 JSON Schema,这正是 FastAPI 能自动生成 Swagger 文档的原因,OpenAI Function Calling 也基于此。

💡
Pydantic 不是 ORM,也不是数据库层,它只关心 数据结构的定义、验证与转换。可以把它想象成「更强大的 dataclass」。
Python — 最简单的 Pydantic 示例
from pydantic import BaseModel

# 1. 定义模型(像写 dataclass 一样)
class User(BaseModel):
    id:    int
    name:  str
    email: str

# 2. 传入原始字典,自动验证 + 转换
user = User(id="42", name="Alice", email="alice@example.com")
# id 被自动从 "42"(str) 转换成 42(int) ✅

print(user.id)    # 42(int,不是字符串)
print(user.name)  # Alice

# 3. 转换成 dict / JSON
print(user.model_dump())  # {'id': 42, 'name': 'Alice', 'email': 'alice@example.com'}
2
为什么要用 Pydantic?

传统 Python 没有强类型,接口边界的数据极容易出错。Pydantic 以极低成本解决了这个问题。

不用 Pydantic 时的痛苦
def create_user(data: dict) -> dict:
    # 手动验证每个字段,重复劳动,还容易漏
    if 'id' not in data:
        raise ValueError("id is required")
    if not isinstance(data['id'], int):
        try:
            data['id'] = int(data['id'])
        except:
            raise ValueError("id must be integer")
    if 'name' not in data or not data['name']:
        raise ValueError("name is required")
    # ... 每个字段都要这样写 😩
    return data

# 错误信息不友好,没有字段路径,没有类型信息
# 嵌套对象更是噩梦...
用 Pydantic 的优雅方式
from pydantic import BaseModel, EmailStr, field_validator

class User(BaseModel):
    id:    int           # 自动验证 + 转换
    name:  str           # 必填
    email: EmailStr     # 格式校验(需 pip install pydantic[email])
    age:   int | None = None  # 可选字段

# 验证失败时,错误信息极其详细:
try:
    User(id="abc", name="", email="not-email")
except ValidationError as e:
    print(e)
# 3 validation errors for User
# id: Input should be a valid integer [type=int_parsing]
# name: String should have at least 1 character
# email: value is not a valid email address
🚀

性能极高

Pydantic v2 核心逻辑用 Rust 重写(pydantic-core),比 v1 快 5-50 倍,是 Python 生态中性能最好的验证库之一。

📖

生态完善

FastAPI、SQLModel、LangChain、OpenAI SDK 等几乎所有主流 Python 框架都以 Pydantic 作为核心依赖。

🧠

IDE 智能感知

基于类型注解,pyright / mypy / VSCode 都能完美补全、类型检查,彻底告别 dict 的 key 地狱。

📐

零学习负担

语法和标准 Python dataclass 几乎相同,5 分钟上手,不需要学新的 DSL 或魔法方法。

3
核心概念地图

Pydantic 的核心由以下几个层次构成,从模型定义到运行时验证,再到序列化输出。

输入数据 dict / JSON str / ORM 对象 BaseModel • 字段声明(类型注解) • Field() 元数据 • @validator / @field_validator • model_config 配置 • 嵌套 / 继承模型 验证引擎 (Rust Core) ✓ 类型验证 ✓ 强制类型转换 ✓ 约束检查 ✓ 自定义验证器 ValidationError 详细错误列表 失败 模型实例 • 强类型字段访问 • .model_dump() • .model_dump_json() • .model_json_schema() 输出 • Python dict • JSON 字符串 • JSON Schema • ORM 模型
4
验证详解

Pydantic 支持多层次验证:内置类型、约束装饰器、自定义验证器、跨字段验证。

内置类型 — Pydantic 支持所有 Python 标准类型
from pydantic import BaseModel
from datetime import datetime, date
from typing import List, Dict, Optional, Literal, Union
from uuid import UUID
from enum import Enum

class Status(str, Enum):
    active   = "active"
    inactive = "inactive"

class Profile(BaseModel):
    user_id:    UUID                    # UUID 格式
    username:   str
    age:        Optional[int] = None   # 可选
    score:      float
    is_vip:     bool                    # "true"/"yes"/1 都能转换
    tags:       List[str]              # 列表中每个元素也会被验证
    metadata:   Dict[str, int]         # 字典键值类型验证
    status:     Status                 # Enum 枚举验证
    created_at: datetime              # ISO 字符串自动转 datetime
    role:       Literal["admin", "user"]  # 字面量限定
约束条件 — 用 Annotated + 约束类型
from pydantic import BaseModel, Field
from typing import Annotated
from pydantic import PositiveInt, NonNegativeFloat

class Product(BaseModel):
    # 字符串约束
    name:     Annotated[str, Field(min_length=1, max_length=100)]
    sku:      Annotated[str, Field(pattern=r'^[A-Z]{2}\d{4}$')]  # 正则

    # 数值约束
    price:    Annotated[float, Field(gt=0, le=99999.99)]  # gt=大于, le=小于等于
    quantity: PositiveInt                               # 内置正整数类型
    discount: NonNegativeFloat = 0.0                   # 非负浮点

    # 列表约束
    categories: Annotated[List[str], Field(min_length=1, max_length=5)]

# 约束说明:
# gt=   大于
# ge=   大于等于
# lt=   小于
# le=   小于等于
# multiple_of=  是...的倍数
# min_length= / max_length=  字符串/列表长度
自定义验证器 — @field_validator (v2) / @validator (v1)
from pydantic import BaseModel, field_validator, ValidationInfo

class UserRegister(BaseModel):
    username: str
    password: str
    age:      int

    @field_validator('username')
    @classmethod
    def username_must_be_valid(cls, v: str) -> str:
        if not v.isalnum():
            raise ValueError('用户名只能包含字母和数字')
        return v.lower()  # 同时做转换

    @field_validator('password')
    @classmethod
    def password_strength(cls, v: str) -> str:
        if len(v) < 8:
            raise ValueError('密码长度至少 8 位')
        if not any(c.isdigit() for c in v):
            raise ValueError('密码必须包含数字')
        return v

    @field_validator('age')
    @classmethod
    def age_check(cls, v: int) -> int:
        if not (0 <= v <= 150):
            raise ValueError(f'年龄 {v} 不合理')
        return v
跨字段验证 — @model_validator
from pydantic import BaseModel, model_validator
from datetime import date

class DateRange(BaseModel):
    start_date: date
    end_date:   date
    label:      str = ""

    @model_validator(mode='after')  # 所有字段验证完之后运行
    def check_date_order(self) -> 'DateRange':
        if self.end_date < self.start_date:
            raise ValueError('end_date 必须大于等于 start_date')
        # 还可以自动生成字段
        if not self.label:
            self.label = f"{self.start_date} ~ {self.end_date}"
        return self

# 测试
r = DateRange(start_date="2024-01-01", end_date="2024-12-31")
print(r.label)  # "2024-01-01 ~ 2024-12-31"(自动生成)
5
Field() — 字段元数据

Field() 是 Pydantic 的字段声明工具,用于设置默认值、文档描述、别名、约束等元数据。

Field() 完整用法
from pydantic import BaseModel, Field
from typing import Annotated

class Article(BaseModel):
    # 基本默认值
    title:    str     = Field(..., description="文章标题")  # ... 表示必填
    content:  str     = Field(..., min_length=10)
    views:    int     = Field(default=0, ge=0)

    # 动态默认值(用 default_factory)
    tags:     List[str] = Field(default_factory=list)  # 每次都是新列表
    created:  datetime  = Field(default_factory=datetime.now)

    # 字段别名 — 输入时用 alias,输出时可控制
    user_id:  int     = Field(..., alias="userId")     # 接受 JSON 中的 userId
    page_num: int     = Field(1, serialization_alias="pageNum")  # 输出时用 pageNum

    # 排除字段(不序列化到 JSON)
    _secret:  str     = Field(default="", exclude=True)

    # 用 Annotated 更现代的写法
    score: Annotated[float, Field(ge=0, le=100, description="评分 0-100")]

# 使用别名时需要开启:
class Config:
    populate_by_name = True  # v2: 允许用字段名或 alias 都能赋值

必填 vs 可选

Field(...) 中的 ...(省略号)表示必填,等同于没有默认值。有 default=default_factory= 则为可选。

alias vs validation_alias

alias 同时影响输入输出;validation_alias 只影响输入解析;serialization_alias 只影响输出序列化。

default_factory 重要性

可变默认值(list/dict)必须用 default_factory,否则所有实例共享同一个对象(Python 经典坑)。

6
嵌套模型 & 继承

Pydantic 支持任意深度的嵌套,子模型也会被自动验证;通过继承可以复用和扩展模型。

嵌套模型
from pydantic import BaseModel
from typing import List

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

class Order(BaseModel):
    order_id: int
    items:    List[str]
    amount:   float

class User(BaseModel):
    name:    str
    address: Address          # 嵌套模型(对象)
    orders:  List[Order]      # 嵌套模型列表

# 传入字典,自动递归验证
user = User(**{
    "name": "张三",
    "address": {"city": "深圳", "province": "广东", "postcode": "518000"},
    "orders": [
        {"order_id": 1, "items": ["iPhone"], "amount": 7999.0},
        {"order_id": 2, "items": ["AirPods"], "amount": 1299.0},
    ]
})

print(user.address.city)          # 深圳
print(user.orders[0].order_id)   # 1(int,非字符串)
继承 — 复用与扩展(常用于 Create/Update/Response 模式)
class UserBase(BaseModel):
    username: str
    email:    str

class UserCreate(UserBase):    # 创建用户(需要密码)
    password: str

class UserUpdate(UserBase):    # 更新用户(所有字段可选)
    username: str | None = None
    email:    str | None = None

class UserResponse(UserBase):  # 返回给前端(含 id,不含密码)
    id:         int
    is_active:  bool
    created_at: datetime

    class Config:
        from_attributes = True  # v2: 允许从 ORM 对象转换
7
序列化输出

Pydantic 模型实例提供了丰富的序列化方法,可以精细控制输出内容和格式。

序列化常用 API(v2)
user = User(id=1, name="Alice", email="alice@example.com")

# 转 dict
user.model_dump()
# → {'id': 1, 'name': 'Alice', 'email': 'alice@example.com'}

# 只输出指定字段
user.model_dump(include={'id', 'name'})
# → {'id': 1, 'name': 'Alice'}

# 排除字段
user.model_dump(exclude={'email'})
# → {'id': 1, 'name': 'Alice'}

# 只输出非 None 字段
user.model_dump(exclude_none=True)

# 使用序列化别名(serialization_alias)
user.model_dump(by_alias=True)

# 直接转 JSON 字符串
user.model_dump_json(indent=2)
# → '{\n  "id": 1,\n  "name": "Alice",\n  ...\n}'

# 生成 JSON Schema(用于 API 文档/AI Function Calling)
User.model_json_schema()
# → {'title': 'User', 'type': 'object', 'properties': {...}, ...}

# 从 JSON 字符串创建实例
user2 = User.model_validate_json('{"id": 2, "name": "Bob", "email": "b@b.com"}')

# 从 ORM 对象创建(需开启 from_attributes=True)
user3 = UserResponse.model_validate(orm_user_obj)
⚠️
v1 vs v2 API 差异:v1 中是 .dict().json(),v2 中改为 .model_dump().model_dump_json()。v2 保持了向后兼容,但 v1 API 在 v2 中被标记为废弃。
8
Pydantic v1 vs v2 核心差异

2023 年发布的 v2 是破坏性更新,核心用 Rust 重写,性能提升巨大,但 API 有多处改动。

对比项 v1 写法 v2 写法
转 dict user.dict() user.model_dump()
转 JSON user.json() user.model_dump_json()
从 dict 创建 User.parse_obj(d) User.model_validate(d)
从 JSON 创建 User.parse_raw(s) User.model_validate_json(s)
JSON Schema User.schema() User.model_json_schema()
字段定义方式 class Config: ... model_config = ConfigDict(...)
验证器 @validator('field') @field_validator('field')
跨字段验证 @root_validator @model_validator(mode='after')
ORM 支持配置 orm_mode = True from_attributes = True
性能 Pure Python ⚡ Rust Core(快 5~50x)
v2 推荐做法:用 model_config = ConfigDict(...) 替代内部 class Config,语义更清晰,工具支持更好。
v2 推荐配置写法
from pydantic import BaseModel, ConfigDict

class User(BaseModel):
    model_config = ConfigDict(
        from_attributes = True,   # 支持 ORM 对象直接转换
        populate_by_name = True,  # 同时接受 alias 和字段名
        str_strip_whitespace = True,  # 字符串自动去首尾空格
        validate_default = True,  # 默认值也走验证
        extra = 'forbid',         # 禁止额外未声明字段(严格模式)
    )

    id:   int
    name: str
9
FastAPI 集成 — Pydantic 的最典型应用场景

FastAPI 100% 基于 Pydantic,它将 Pydantic 模型直接用作请求体解析、参数验证和响应序列化,同时自动生成 OpenAPI 文档。

FastAPI + Pydantic 完整示例
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field, EmailStr
from typing import Optional

app = FastAPI()

# ① 请求体模型
class UserCreate(BaseModel):
    username: str      = Field(..., min_length=3, max_length=20)
    email:    EmailStr = Field(..., description="用户邮箱")
    password: str      = Field(..., min_length=8)

# ② 响应模型(不暴露密码)
class UserResponse(BaseModel):
    id:       int
    username: str
    email:    str

# ③ 路由 —— FastAPI 自动:
#   a) 解析请求体并用 UserCreate 验证
#   b) 用 UserResponse 序列化返回值
#   c) 在 /docs 中自动生成 Swagger 文档
@app.post("/users", response_model=UserResponse)
async def create_user(user: UserCreate):
    # user 已经是验证通过的 UserCreate 实例
    db_user = save_to_db(user)
    return db_user  # 自动过滤 password 字段
🔗
response_model 的威力:FastAPI 会用 response_model 指定的 Pydantic 模型来过滤返回值,确保敏感字段(如 password、token)不会泄露,即使你的函数返回了完整的数据库对象。
10
最佳实践 & 常见模式

在实际项目中积累的 Pydantic 使用模式与避坑指南。

实用技巧:自定义类型复用
from pydantic import BaseModel, Field
from typing import Annotated

# ✅ 定义可复用的约束类型
UserId    = Annotated[int,   Field(gt=0)]
UserName  = Annotated[str,   Field(min_length=2, max_length=50)]
Score     = Annotated[float, Field(ge=0, le=100)]
PageSize  = Annotated[int,   Field(default=20, ge=1, le=100)]

# 在多个模型中复用
class UserProfile(BaseModel):
    id:    UserId
    name:  UserName
    score: Score

class Leaderboard(BaseModel):
    user_id: UserId    # 复用同一约束
    score:   Score     # 复用同一约束
    page:    PageSize