Pydantic 是 Python 中最流行的数据验证与设置管理库,基于 Python 类型提示(Type Hints)实现。它能够:
定义数据模型(Model),自动进行类型校验
解析和转换输入数据(如 JSON、字典)为模型实例
输出标准化数据(序列化、JSON)
提供强大的错误处理机制
Pydantic 在 FastAPI、Django Ninja 等框架中作为核心数据验证组件被广泛使用。目前主流版本为 Pydantic V2,性能大幅提升,且与 V1 有部分 API 差异,本文以 V2 为例。
安装:
如果需要与 JSON 或 YAML 配合,可安装扩展:
pip install pydantic[email] # 支持邮箱验证等
二、基础模型定义
Pydantic 的核心是 BaseModel 的子类。通过类型提示定义字段,即可自动获得验证、解析、序列化能力。
from pydantic import BaseModelclass User(BaseModel): id: int name: str age: int = 18 # 带默认值 email: str | None = None # 可选字段# 实例化user = User(id=1, name="Alice", age=25)print(user)# 输出:id=1 name='Alice' age=25 email=None# 类型不匹配时自动转换(如果可以)user2 = User(id="2", name="Bob", age="30")print(user2)# 输出:id=2 name='Bob' age=30 email=None# 无效数据会抛出 ValidationErrortry: User(id="abc", name="Charlie")except Exception as e: print(e)
三、字段类型与约束
Pydantic 支持 Python 原生类型(int, str, float, bool, list, dict 等),以及标准库 typing 中的泛型(List, Dict, Optional, Union)。此外还提供了丰富的内置类型(EmailStr, HttpUrl, PositiveInt 等)。
1. 基本类型与泛型
from typing import List, Dict, Optional, Unionfrom pydantic import BaseModelclass Data(BaseModel): names: List[str] # 字符串列表 scores: Dict[str, int] # 键为字符串,值为整数的字典 flag: Optional[bool] # 可以是布尔值或 None mixed: Union[int, str] # 整数或字符串data = Data(names=["a", "b"], scores={"math": 95}, flag=True, mixed="hello")print(data)
2. 内置校验类型
Pydantic 在 pydantic.types 中提供了许多有约束的类型。
from pydantic import BaseModel, EmailStr, HttpUrl, PositiveInt, conint, conlistclass Profile(BaseModel): email: EmailStr # 必须是邮箱格式 homepage: HttpUrl # 必须是 http/https URL age: PositiveInt # 正整数 score: conint(ge=0, le=100) # 0~100 之间的整数 tags: conlist(str, min_length=1, max_length=5) # 长度1~5的字符串列表# 正确profile = Profile( email="test@example.com", homepage="https://pydantic.dev", age=30, score=85, tags=["a", "b", "c"])print(profile)# 错误示例try: Profile( email="not-an-email", homepage="ftp://invalid", age=-5, score=150, tags=[] )except Exception as e: print(e)
常用约束类型(con* 函数):
conint(gt=0, lt=100) – 整数范围
confloat(ge=0.0, le=1.0) – 浮点数范围
constr(min_length=3, max_length=10, pattern=r'^[A-Z]') – 字符串长度与正则
conlist(item_type=int, min_length=2) – 列表约束
condict(...) – 字典约束
四、字段默认值与工厂函数
字段可以设置静态默认值,也可以使用 default_factory 来动态生成默认值(例如列表或复杂对象)。
from pydantic import BaseModel, Fieldfrom uuid import uuid4from datetime import datetimeclass Item(BaseModel): id: str = Field(default_factory=lambda: str(uuid4())) created_at: datetime = Field(default_factory=datetime.now) tags: list[str] = Field(default_factory=list) # 每次实例化生成新列表item1 = Item()item2 = Item()print(item1.id != item2.id) # Trueprint(item1.tags is not item2.tags) # True(不同实例列表独立)
Field 函数还可以添加额外信息,如标题、描述、示例值等,这些信息在生成 JSON Schema 时会用到。
classProduct(BaseModel): name: str = Field(..., title="Product name", max_length=50) price: float = Field(..., gt=0, description="Price in USD")
五、验证器
Pydantic V2 提供两种验证器:字段验证器(@field_validator)和模型验证器(@model_validator)。验证器可以在数据赋值时进行自定义逻辑。
1. 字段验证器
针对单个字段进行校验或转换。
from pydantic import BaseModel, Field, field_validator, ValidationInfoclass User(BaseModel): name: str age: int @field_validator("name") @classmethod def name_must_be_capitalized(cls, v: str) -> str: if not v[0].isupper(): raise ValueError("Name must start with uppercase") return v @field_validator("age") @classmethod def check_age(cls, v: int, info: ValidationInfo) -> int: if v < 0: raise ValueError("Age cannot be negative") if v > 150: raise ValueError("Age too high") return vtry: user = User(name="alice", age=200)except Exception as e: print(e) # 会同时抛出多个验证错误
2. 模型验证器
可以访问所有字段,进行跨字段验证,或在验证后修改模型。
from pydantic import BaseModel, model_validatorclass Order(BaseModel): quantity: int price: float total: float | None = None @model_validator(mode="after") def compute_total(self) -> "Order": self.total = self.quantity * self.price return selforder = Order(quantity=3, price=19.99)print(order.total) # 59.97
mode="before" 的验证器可以在解析前处理原始输入(例如字典),mode="after" 则在字段验证后执行。
六、嵌套模型
Pydantic 支持模型嵌套,子模型同样会进行验证。
from pydantic import BaseModelclass Address(BaseModel): street: str city: str zipcode: strclass Employee(BaseModel): name: str address: Address # 嵌套模型 projects: list[str]data = { "name": "John", "address": {"street": "123 Main St", "city": "Boston", "zipcode": "02101"}, "projects": ["Project X", "Project Y"]}emp = Employee(**data)print(emp.address.city) # Boston# 也可以直接传入 Address 实例emp2 = Employee(name="Jane", address=Address(street="456 Oak", city="NY", zipcode="10001"), projects=[])
七、数据解析与转换
Pydantic 模型可以从多种格式创建实例:
字典:Model(**dict)
JSON 字符串:Model.model_validate_json(json_str)
其他对象:Model.model_validate(obj)
import jsonclass Person(BaseModel): name: str age: int# 从字典p1 = Person.model_validate({"name": "Alice", "age": 30})# 从 JSONjson_str = '{"name": "Bob", "age": 25}'p2 = Person.model_validate_json(json_str)# 从任意对象(只要有 name 和 age 属性)class Dummy: name = "Charlie" age = 40p3 = Person.model_validate(Dummy())
注意:model_validate 与 model_validate_json 是 V2 的新 API,替代了 V1 的 parse_obj 和 parse_raw。
八、序列化与导出
模型实例可以导出为多种格式。
1. 字典和 JSON
user = User(id=1, name="Alice")# 导出字典print(user.model_dump()) # {'id': 1, 'name': 'Alice', 'age': 18, 'email': None}# 仅包含某些字段print(user.model_dump(include={"id", "name"}))# 排除某些字段print(user.model_dump(exclude={"age"}))# 导出 JSONprint(user.model_dump_json()) # {"id":1,"name":"Alice","age":18,"email":null}print(user.model_dump_json(indent=2)) # 格式化 JSON
2. 控制序列化行为
通过 model_dump 的参数可以控制是否包含默认值、排除 None 等。
class ConfigModel(BaseModel): a: int = 1 b: str | None = Nonem = ConfigModel()# 默认包含默认值和 Noneprint(m.model_dump()) # {'a': 1, 'b': None}# 排除 Noneprint(m.model_dump(exclude_none=True)) # {'a': 1}# 排除未设置的字段(即没有显式赋值,且是默认值)print(m.model_dump(exclude_unset=True)) # {}
3. 自定义序列化器
可以使用 @field_serializer 或 @model_serializer 控制导出方式。
from pydantic import BaseModel, field_serializerclass User(BaseModel): name: str password: str @field_serializer("password") def hide_password(self, v: str) -> str: return "***"user = User(name="Alice", password="secret")print(user.model_dump()) # {'name': 'Alice', 'password': '***'}
九、配置选项(ConfigDict)
Pydantic V2 使用 model_config 属性(ConfigDict 对象)来配置模型行为。常用配置:
from pydantic import BaseModel, ConfigDictclass MyModel(BaseModel): name: str model_config = ConfigDict( extra="forbid", # 禁止额外字段 str_strip_whitespace=True,# 自动去除字符串首尾空格 validate_default=True, # 验证默认值 frozen=True, # 模型实例不可变(类似 dataclass frozen) json_schema_extra={ # 添加自定义 JSON Schema 信息 "description": "A sample model" } )# 额外字段会抛出错误try: m = MyModel(name="Alice", extra_field=123)except Exception as e: print(e) # Extra inputs are not permitted
常见配置项:
extra: "forbid", "ignore", "allow" – 控制模型对额外输入字段的处理
str_strip_whitespace: 自动去除字符串字段的前后空白
frozen: 是否冻结模型(不可修改属性)
validate_default: 是否验证默认值
json_schema_extra: 添加额外的 JSON Schema 元数据
十、高级用法
1. 动态创建模型
使用 create_model 函数在运行时生成模型。
from pydantic import create_modelDynamicModel = create_model( "DynamicModel", name=(str, ...), # 必填字段 age=(int, 0), # 可选,默认0 __config__=None # 可传入 ConfigDict)obj = DynamicModel(name="Tom")print(obj) # name='Tom' age=0
2. 自定义数据类型
通过继承 pydantic.GetCoreSchemaHandler 或使用 Annotated 配合 AfterValidator 可以创建自定义校验逻辑。
from typing import Anyfrom pydantic import BaseModel, AfterValidator, Fieldfrom typing_extensions import Annotateddef is_even(v: int) -> int: if v % 2 != 0: raise ValueError("Number must be even") return vEvenInt = Annotated[int, AfterValidator(is_even)]class Demo(BaseModel): even_number: EvenIntdemo = Demo(even_number=4) # OKtry: demo = Demo(even_number=5)except Exception as e: print(e)
3. 与 dataclass 结合
Pydantic 提供 @pydantic.dataclasses.dataclass 装饰器,使普通 dataclass 具备验证能力。
from pydantic.dataclasses import dataclass@dataclassclass Point: x: int y: intp = Point(x="1", y=2) # 自动转换print(p) # Point(x=1, y=2)
4. 与 typing 模块的高级类型
Pydantic 支持 Literal, TypedDict, Union 等。
from typing import Literal, Unionfrom pydantic import BaseModelclass StatusModel(BaseModel): status: Literal["active", "inactive"] data: Union[int, str] # 可以是整数或字符串m = StatusModel(status="active", data=100)
十一、性能与最佳实践
使用 V2:V2 的验证速度比 V1 快数倍(基于 Rust 的 pydantic-core)。
避免在验证器中做耗时操作:验证器会在每次实例化或解析时执行,如有复杂计算,考虑放在 model_validator 中并缓存。
善用 model_validate 而非 __init__:前者提供更多控制(如 strict 模式)。
合理使用 frozen 和 extra:根据业务需求配置模型的不可变性及额外字段处理。
类型提示尽量精确:使用 list[str] 而非 list,使用 Optional 表达可选性。
十二、与 FastAPI 集成示例
FastAPI 直接使用 Pydantic 模型作为请求/响应体。
from fastapi import FastAPIfrom pydantic import BaseModelapp = FastAPI()class Item(BaseModel): name: str price: float tax: float | None = None@app.post("/items/")async def create_item(item: Item): return {"item": item, "total": item.price + (item.tax or 0)}
FastAPI 自动将请求 JSON 转换为 Item 实例,并生成 OpenAPI 文档。
十三、总结
Pydantic 是现代 Python 开发中处理数据验证和解析的利器。其核心优势包括:
基于类型提示:简洁、直观
自动类型转换:减少重复代码
强大的验证器体系:灵活应对复杂业务规则
高性能:V2 大幅提升速度
丰富的生态:与 FastAPI、SQLModel 等无缝集成
掌握 Pydantic 不仅能提升代码健壮性,还能显著提高开发效率。建议在项目中广泛使用,并结合官方文档深入学习更高级的特性。