Pydantic 数据校验:Python 类型注解与模型
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]、Optional、Union等复杂泛型。 - 你可以组合并嵌套模型,构建出描述任意 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
常用约束:gt、lt、ge、le 用于数值;min_length、max_length、pattern 用于字符串;default、default_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 文件等处读取并校验,类型转换完全透明。
高性能与最佳实践
- 优先使用 v2 模型,享受 Rust 核心带来的速度提升。
- 严格模式:对于外部输入,总是设置
extra="forbid",防止静默忽略错误数据。 - 复用模型:对相似的请求体和响应体使用继承或组合。
- 自定义类型:对于重复出现的复杂校验,可以定义 Pydantic 自定义类型(
Annotated)。 - 保持模型纯净:避免在模型中加入业务逻辑方法,模型仅负责数据结构与校验。
- 利用
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 重构一个现有的数据源处理模块,亲身体验错误提前暴露、代码量减少的乐趣。