Pydantic 数据校验:Python 类型注解与模型

FreeGuideOnline 最新 2026-06-16

Pydantic 数据校验完全指南:Python 类型注解与模型

为什么需要 Pydantic

在现代 Python 开发中,我们经常需要处理来自外部的数据——API 请求体、配置文件、环境变量、数据库记录。这些数据本质上都是非结构化的字典、列表或字节串。如果不加以校验,一个拼写错误的字段名、一个类型不符的值,就可能在代码深处引发难以排查的异常。

Pydantic 正是为解决这一痛点而生:它利用 Python 的类型注解,在运行时对数据进行严格校验与转换,让你可以像书写类型提示一样定义数据模型,并自动获得错误清晰、可序列化的校验结果。

核心理念:用声明式的模型定义替代手写的大量 if isinstance(...) 检查。

Pydantic v2 与 v1 的区别

本文基于 Pydantic v2,它由 Rust 核心(pydantic-core)重写,性能提升 5-50 倍,并且 API 更加严格一致。如果你正在使用 v1,建议尽快迁移,本文的方法同样适合快速上手。

安装与快速开始

pip install pydantic

最简单的模型是一个继承自 BaseModel 的类,仅包含带类型注解的属性:

from pydantic import BaseModel

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

当实例化时,Pydantic 会自动校验并转换数据:

user = User(id="123", name="Alice", email="alice@example.com")
print(user.id)        # 123 (自动从 str 转为 int)
print(user.model_dump())  # {'id': 123, 'name': 'Alice', ...}

如果传入非法类型,会立即抛出 ValidationError,并给出清晰的位置与原因。

Pydantic 与 Python 类型注解的关系

Pydantic 不是类型检查工具(如 mypy),而是 运行时数据校验引擎。它利用标准库 typing 模块中的类型作为校验规则基础,同时扩展了大量的自定义类型。

这意味着:

  • 你书写的类型注解既服务于静态分析,也驱动运行时校验。
  • 支持 list[int]dict[str, float]OptionalUnion 等复杂泛型。
  • 你可以组合并嵌套模型,构建出描述任意 JSON 结构的数据模型。

定义模型:从简单字段到嵌套结构

基础字段类型

from typing import List, Optional
from pydantic import BaseModel

class Item(BaseModel):
    name: str
    price: float
    tags: List[str] = []         # 可变默认值安全,Pydantic 会深拷贝
    note: Optional[str] = None

嵌套模型

Pydantic 自动推断嵌套模型并进行递归校验:

class Supplier(BaseModel):
    name: str
    contact_email: str

class Product(BaseModel):
    sku: str
    stock: int
    supplier: Supplier

# 传入嵌套字典即可
data = {
    "sku": "P123",
    "stock": 50,
    "supplier": {"name": "Acme", "contact_email": "acme@corp.com"}
}
product = Product(**data)
print(product.supplier.name)  # Acme

数据校验功能深入

字段级约束:使用 Field

通过 Field() 函数可以为字段添加额外校验与元数据:

from pydantic import BaseModel, Field, EmailStr

class SignupForm(BaseModel):
    username: str = Field(..., min_length=3, max_length=50, pattern="^[a-zA-Z0-9_]+$")
    email: EmailStr
    age: int = Field(ge=18, le=120, description="用户年龄")
    password: str = Field(min_length=8)

# EmailStr 需要安装 pydantic[email] 或 email-validator

常用约束:gtltgele 用于数值;min_lengthmax_lengthpattern 用于字符串;defaultdefault_factory 设置默认值。

自定义校验器

当内置约束不够用时,使用 @field_validator@model_validator

字段级验证(v2 风格):

from pydantic import BaseModel, field_validator

class Order(BaseModel):
    product_id: str
    quantity: int

    @field_validator('product_id')
    @classmethod
    def check_product_id(cls, v: str) -> str:
        if not v.startswith('PROD-'):
            raise ValueError('产品编号必须以 PROD- 开头')
        return v.strip()

    @field_validator('quantity')
    @classmethod
    def quantity_positive(cls, v: int) -> int:
        if v <= 0:
            raise ValueError('数量必须为正数')
        return v

模型级验证(需要访问多个字段):

from pydantic import BaseModel, model_validator

class TemperatureRange(BaseModel):
    min_temp: float
    max_temp: float

    @model_validator(mode='after')
    def check_range(self):
        if self.min_temp > self.max_temp:
            raise ValueError('min_temp 不能大于 max_temp')
        return self

复杂类型:Union、Literal、枚举

from typing import Union, Literal
from enum import Enum

class PaymentStatus(str, Enum):
    PENDING = "pending"
    PAID = "paid"
    FAILED = "failed"

class Payment(BaseModel):
    amount: float
    currency: Literal["USD", "EUR", "CNY"]
    status: PaymentStatus
    metadata: Union[dict, None] = None

使用 str, Enum 继承使得枚举值可以直接比对,且序列化时输出其值而非枚举对象。

配置与模型行为

通过内部类 model_config 控制全局行为:

class User(BaseModel):
    model_config = {
        "str_strip_whitespace": True,      # 自动去除首尾空格
        "validate_assignment": True,       # 赋值时也校验
        "extra": "forbid",                 # 禁止未声明的字段
        "use_enum_values": True,
    }
    name: str
    tags: list[str] = []

extra 的选项:

  • "ignore":忽略多余字段
  • "allow":接受多余字段并存储
  • "forbid":存在多余字段时抛出错误(推荐用于严格 API)

数据序列化与反序列化

Pydantic 模型提供了丰富的导出方法:

方法 说明
model_dump() 转为字典(v2 替代 dict()
model_dump_json() 转为 JSON 字符串,保证 JSON 安全类型
model_copy() 浅拷贝/深拷贝副本
user = User(name="Alice", email="alice@example.com")
json_str = user.model_dump_json(indent=2)  # 美化输出

支持自定义编码器,例如将 datetime 转为 ISO 格式字符串:

from datetime import datetime
from pydantic import BaseModel

class Event(BaseModel):
    timestamp: datetime

    model_config = {
        "json_encoders": {
            datetime: lambda v: v.isoformat()
        }
    }

与 FastAPI 等框架的集成

FastAPI 深度集成 Pydantic,将请求体、查询参数、路径参数自动解析为 Pydantic 模型并校验。你只需定义模型,即可获得自动生成的 OpenAPI 文档与交互式 API 页面。

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float

@app.post("/items/")
def create_item(item: Item):
    return {"item_name": item.name, "price_with_tax": item.price * 1.1}

错误处理与调试

Pydantic 的 ValidationError 包含多个错误条目,每个条目指明位置(字段路径)、错误类型和上下文。你可以捕获并解析以向客户端返回友好的错误消息:

from pydantic import ValidationError

try:
    User(name="A", email="not-an-email")
except ValidationError as e:
    print(e.errors())
    # [{'type': 'value_error.email', 'loc': ('email',), 'msg': '...', ...}]

通过 e.errors() 得到的结构化列表可以直接用于 HTTP 400 响应体。

实战:构建强类型配置管理器

一个常见的需求:从环境变量或 YAML 文件中加载配置,并立即校验其完整性。使用 Pydantic 的 BaseSettings(在 pydantic-settings 中)可以轻松实现。

pip install pydantic-settings
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    model_config = {"env_file": ".env", "extra": "ignore"}

    database_url: str
    secret_key: str
    debug: bool = False
    allowed_hosts: list[str] = ["localhost"]

settings = Settings()

BaseSettings 会自动从环境变量、.env 文件等处读取并校验,类型转换完全透明。

高性能与最佳实践

  1. 优先使用 v2 模型,享受 Rust 核心带来的速度提升。
  2. 严格模式:对于外部输入,总是设置 extra="forbid",防止静默忽略错误数据。
  3. 复用模型:对相似的请求体和响应体使用继承或组合。
  4. 自定义类型:对于重复出现的复杂校验,可以定义 Pydantic 自定义类型(Annotated)。
  5. 保持模型纯净:避免在模型中加入业务逻辑方法,模型仅负责数据结构与校验。
  6. 利用 TypeAdapter:在某些无需完整模型的情况下(如校验单个 list[int]),使用 TypeAdapter 提高效率。
from pydantic import TypeAdapter

adapter = TypeAdapter(list[int])
result = adapter.validate_python(["1", "2", 3])  # [1, 2, 3]

总结

Pydantic 将 Python 的类型注解从静态检查维度延伸至运行时数据保障,让你以最小的附加代码量获得工业级的数据校验能力。无论是构建 Web API、处理配置,还是清洗异构数据,它都能大幅降低“脏数据”引发的故障,同时提升代码的可读性和可维护性。从基础的 BaseModel 到深度自定义校验器,Pydantic 提供了一个渐进式的学习路径,即使初学者也能在几分钟内上手,并随着项目复杂度逐步深入使用高级特性。

下一步建议:尝试用 Pydantic 重构一个现有的数据源处理模块,亲身体验错误提前暴露、代码量减少的乐趣。