Pydantic 是目前 Python 生态中最常用的数据校验库之一。它的核心思路是把数据模型声明成带类型注解的类,然后让框架自动完成数据校验、类型转换和序列化。对于需要处理 API 请求、配置文件或数据库记录的开发者来说,Pydantic 能显著减少手写校验代码的量。
在 FastAPI 中,Pydantic 是默认的请求体和响应模型基础。除此之外,它在独立脚本、后台任务以及配置管理模块里也很实用。下面从安装开始,逐步梳理 Pydantic V2 的核心用法。
安装后可以通过一行命令确认版本:
- python -c "import pydantic; print(pydantic.__version__)"
复制代码
一、定义模型与基本校验
Pydantic 的数据模型通过继承 BaseModel 创建,类属性必须带类型注解。创建实例时,框架会自动校验传入的数据:
- from pydantic import BaseModel
- class User(BaseModel):
- name: str
- age: int
- email: str
- user = User(name="张三", age=25, email="zhangsan@example.com")
- print(user)
复制代码
如果传入值的类型不匹配,但可以进行转换,Pydantic 会执行自动类型转换。比如 age 传入字符串 "30" 时,最终会变成整数 30:
- user = User(name="李四", age="30", email="lisi@example.com")
- print(user.age, type(user.age))
复制代码
如果数据完全无法通过校验,比如 age 传入 "abc",Pydantic 会抛出 ValidationError。实际项目中通常用 try/except 捕获这个异常并返回给调用方:
- from pydantic import ValidationError
- try:
- User(name="王五", age="abc", email="wangwu@example.com")
- except ValidationError as e:
- print(e)
复制代码
二、常用字段类型与约束
Pydantic 支持 Python 标准类型:str、int、float、bool、list、dict、tuple、set,也支持 typing 模块中的 Optional、List、Dict、Union 等。比如下面的 Order 模型:
- from typing import Optional, List, Dict, Union
- from pydantic import BaseModel
- class Order(BaseModel):
- order_id: int
- items: List[str]
- metadata: Dict[str, str]
- discount: Optional[float] = None
- status: Union[str, int] = "pending"
复制代码
字段级约束通过 Field 函数实现。常见参数包括字符串长度、数值大小范围和正则匹配。下面是一个 Product 模型示例:
- from pydantic import BaseModel, Field
- class Product(BaseModel):
- name: str = Field(..., min_length=1, max_length=50)
- price: float = Field(..., gt=0, le=10000)
- quantity: int = Field(0, ge=0)
- description: str = Field(default="", max_length=200)
复制代码
约束参数的语义如下:min_length/max_length 控制字符串长度;gt/ge/lt/le 分别表示大于、大于等于、小于、小于等于;pattern 用于正则校验;default_factory 可以生成动态默认值。
正则表达式在用户输入校验中非常实用。比如用户名只能包含字母数字和下划线,手机号需要匹配国内手机号规则:
- from pydantic import BaseModel, Field
- class Account(BaseModel):
- username: str = Field(..., pattern=r"^[a-zA-Z0-9_]{3,20}$")
- phone: str = Field(..., pattern=r"^1[3-9]\d{9}$")
复制代码
三、验证器:处理复杂校验逻辑
当内置约束不够用时,可以编写自定义验证器。V2 版本使用 field_validator 装饰器处理字段级校验,注意验证器需要定义成类方法:
- from pydantic import BaseModel, field_validator
- class Registration(BaseModel):
- username: str
- password: str
- confirm_password: str
- @field_validator("username")
- @classmethod
- def username_not_admin(cls, v: str) -> str:
- if v.lower() == "admin":
- raise ValueError("用户名不能为 admin")
- return v
- @field_validator("confirm_password")
- @classmethod
- def passwords_match(cls, v: str, info) -> str:
- if "password" in info.data and v != info.data["password"]:
- raise ValueError("两次输入的密码不一致")
- return v
复制代码
字段级验证器可以拿到当前字段值,也可以通过 info.data 访问其他已经校验过的字段值,因此用于确认密码之类的场景比较方便。
如果校验逻辑涉及到多个字段的联动,比如日期范围,可以使用 model_validator。mode="after" 表示验证器在字段校验完成后执行,可以访问模型实例的完整数据:
- from pydantic import BaseModel, model_validator
- class DateRange(BaseModel):
- start_date: str
- end_date: str
- @model_validator(mode="after")
- def check_date_range(self):
- if self.start_date > self.end_date:
- raise ValueError("开始日期不能晚于结束日期")
- return self
复制代码
四、序列化与解析
Pydantic V2 中,model_dump() 用于把模型转换成字典,model_dump_json() 用于转换成 JSON 字符串。解析字典和 JSON 字符串则分别使用 model_validate() 和 model_validate_json()。
- user = User(name="张三", age=25, email="zhangsan@example.com")
- data = user.model_dump()
- print(data)
- # {'name': '张三', 'age': 25, 'email': 'zhangsan@example.com'}
- json_str = user.model_dump_json()
- print(json_str)
- # {"name":"张三","age":25,"email":"zhangsan@example.com"}
复制代码
反向操作示例:
- data_dict = {"name": "李四", "age": 30, "email": "lisi@example.com"}
- user = User.model_validate(data_dict)
- json_str = '{"name": "王五", "age": 28, "email": "wangwu@example.com"}'
- user = User.model_validate_json(json_str)
复制代码
五、嵌套模型
面对层级化数据,Pydantic 支持模型嵌套。子模型可以直接作为父模型字段的类型声明,实例化父模型时传入子模型实例或字典均可:
- from typing import List
- from pydantic import BaseModel
- class Address(BaseModel):
- city: str
- street: str
- zip_code: str
- class Customer(BaseModel):
- name: str
- address: Address
- orders: List[dict] = []
- customer = Customer(
- name="赵六",
- address={"city": "北京", "street": "中关村大街", "zip_code": "100080"},
- orders=[{"order_id": 1, "amount": 99.9}],
- )
- print(customer.address.city)
复制代码
嵌套模型让 API 请求体的复杂结构可以保持清晰,同时自动完成深层校验。
六、配置管理:Settings 模型
Pydantic 官方把配置读取功能独立到了 pydantic-settings 包中。使用前需要先安装:
- pip install pydantic-settings
复制代码
这个模块可以从环境变量或 .env 文件自动加载配置,适合和项目部署环境配合:
- from pydantic_settings import BaseSettings
- class Settings(BaseSettings):
- app_name: str = "My App"
- debug: bool = False
- database_url: str
- class Config:
- env_file = ".env"
- settings = Settings()
- print(settings.database_url)
复制代码
运行时,Pydantic 会按照环境变量和 .env 文件的内容填充 Settings 中的字段,无需手动解析配置文件。
七、FastAPI 实战:用户注册接口
Pydantic 最常见的落地场景是配合 FastAPI 开发 REST API。在下面的示例中,FastAPI 会自动使用 Pydantic 模型解析请求体、校验字段,并根据 response_model 约束响应格式:
- from fastapi import FastAPI
- from pydantic import BaseModel, EmailStr, Field
- app = FastAPI()
- class UserRegister(BaseModel):
- username: str = Field(..., min_length=3, max_length=20)
- email: EmailStr
- password: str = Field(..., min_length=8)
- class UserResponse(BaseModel):
- username: str
- email: EmailStr
- @app.post("/register", response_model=UserResponse)
- async def register(user: UserRegister):
- # 模拟用户创建逻辑
- return UserResponse(username=user.username, email=user.email)
复制代码
这里的 EmailStr 是 Pydantic 提供的 Email 类型,需要单独安装 email-validator 才能使用。在 FastAPI 接口中声明 UserRegister 参数后,请求体会在进入路由函数之前完成校验。校验失败时 FastAPI 会返回 422 错误和字段错误详情,无需开发者手动处理 ValidationError。
八、实际使用建议
从简单模型入手,先用 Field 里的内置约束解决大部分问题;遇到跨字段或复杂业务逻辑时再补充验证器。面对嵌套较多、深度较深的配置结构,可以优先拆分成多个子模型。
需要注意的是,Pydantic V2 的 API 与 V1 不同。V1 中的 parse_obj、parse_raw、dict()、json() 等旧方法在 V2 中已被 model_validate、model_validate_json、model_dump、model_dump_json 取代。如果项目是从旧版本升级,需要同步修改这些调用。另外,V2 的核心校验逻辑使用 Rust 实现,性能比纯 Python 方案高很多,适合在数据量较大的场景下替代手写校验。
Pydantic 的另一个价值在于和类型检查工具配合。定义的模型类同时承担了类型声明和运行时校验的双重职责,开发时能获得 IDE 的类型补全,运行时又能得到严格数据保证。当项目使用 FastAPI、Django、SQLAlchemy 等技术栈时,Pydantic 都能作为数据层与业务层之间的稳定桥梁。 |