【Pydantic】BaseModel 使用教程与实践
引言
在 Python 项目开发中,数据校验是保障程序稳定性的核心环节。传统方式依赖大量 if-else 条件判断,不仅代码冗余、可读性差,还容易出现校验遗漏、规则不统一等问题。尤其在接口开发、数据解析、业务参数校验场景中,这些问题极易引发线上 BUG。
Pydantic 是 Python 生态中主流的数据校验库,依托 Python 类型提示机制,能够快速实现结构化数据校验、类型自动转换、异常精准捕获。它已成为 FastAPI、LangChain、Haystack 等框架的底层数据基础。
本文核心内容:
- Pydantic 的核心概念与基础用法
- 字段约束与自定义校验器
- 嵌套模型与复杂数据结构
- 数据验证方法(
model_validate/model_validate_json) - 实战场景与最佳实践
- 常见问题与避坑指南
本文基于 Pydantic v2 版本,v2 相较于 v1 性能提升数倍,语法更简洁,是当前生产环境的主流版本。
一、Pydantic 是什么?
1.1 核心概念
Pydantic 是一个基于 Python 类型提示的数据验证库。它的核心理念是:
用 Python 的类型注解定义数据结构,Pydantic 自动负责验证、转换和序列化。
当你定义一个 Pydantic 模型时,实际上是声明了数据的"形状"——字段名称、类型、约束条件。当真实数据传入时,Pydantic 会:
- 验证:检查数据是否符合类型和约束
- 转换:自动将数据转换为声明类型(如字符串
"123"转为整数123) - 报错:如果验证失败,抛出详细的
ValidationError
1.2 为什么选择 Pydantic?
| 优势 | 说明 |
|---|---|
| 类型安全 | 基于 Python 类型提示,与 IDE 和类型检查器完美集成 |
| 自动验证 | 声明即校验,无需编写大量 if-else |
| 自动类型转换 | 宽松模式自动转换兼容类型,如字符串转整数 |
| 详细错误信息 | 验证失败时提供精确的错误定位和原因 |
| 序列化支持 | 一键导出字典或 JSON(model_dump / model_dump_json) |
| 高性能 | v2 基于 Rust 编写的 pydantic-core,性能提升显著 |
二、快速上手:第一个 Pydantic 模型
2.1 安装
pip install pydantic
2.2 定义模型
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str
email: str
age: int = 18 # 带默认值,可选
2.3 创建实例
# 正常创建
user = User(id=1, name="张三", email="zhangsan@example.com")
print(user)
# id=1 name='张三' email='zhangsan@example.com' age=18
# 自动类型转换:字符串 "2" 转为整数 2
user2 = User(id="2", name="李四", email="lisi@example.com")
print(user2.id) # 2 (int)
# 验证失败:抛出 ValidationError
try:
user3 = User(id="not_a_number", name="王五", email="wangwu@example.com")
except ValidationError as e:
print(e)
"""
1 validation error for User
id
Input should be a valid integer, unable to parse string as an integer
[type=int_parsing, input_value='not_a_number', input_type=str]
"""
三、模型与字段详解
3.1 基础模型定义
Pydantic 模型的核心就是继承 BaseModel,用类型注解声明字段:
from pydantic import BaseModel
class Address(BaseModel):
street: str
city: str
state: str
zip_code: str
country: str = "US" # 可选,默认 "US"
apartment: str | None = None # 可选,默认 None
addr = Address(
street="123 Main St",
city="Springfield",
state="IL",
zip_code="62704",
)
print(addr)
# street='123 Main St' city='Springfield' state='IL' zip_code='62704' country='US' apartment=None
规则说明:
- 无默认值字段:必填
- 有默认值字段:可选
- 类型为 T | None 且默认 None:可选
3.2 Field 字段约束
当简单类型注解不足以满足需求时,使用 Field 添加约束:
from pydantic import BaseModel, Field
class Product(BaseModel):
name: str = Field(
min_length=1,
max_length=200,
title="Product Name",
description="商品显示名称",
examples=["Widget Pro"]
)
sku: str = Field(
pattern=r"^[A-Z]{2,4}-\d{4,8}$",
description="库存单位,格式 'XX-0000'"
)
price: float = Field(
gt=0,
le=999_999.99,
description="美元价格,必须为正"
)
quantity: int = Field(
default=0,
ge=0,
description="库存数量,不可为负"
)
常用 Field 参数:
| 参数 | 说明 |
|---|---|
min_length / max_length |
字符串长度限制 |
pattern |
正则表达式校验 |
gt / ge |
大于 / 大于等于(数值) |
lt / le |
小于 / 小于等于(数值) |
default |
默认值 |
title / description |
元数据(用于文档生成) |
examples |
示例值 |
validation_alias |
输入时的字段别名 |
3.3 字段别名
当输入字段名与模型字段名不一致时,使用 validation_alias:
from pydantic import BaseModel, Field, ConfigDict
class Product(BaseModel):
model_config = ConfigDict(populate_by_name=True) # 允许同时使用字段名和别名
name: str = Field(validation_alias="product_name")
category: str = Field(validation_alias="product_category")
# 使用别名传入
product = Product(product_name="Widget", product_category="Electronics")
print(product.name) # Widget
print(product.category) # Electronics
# 也可以使用字段名(由于 populate_by_name=True)
product2 = Product(name="Gadget", category="Tools")
四、数据验证方法
Pydantic 提供了多种验证方法,适用于不同数据来源:
4.1 model_validate():字典/对象验证
from datetime import datetime
from pydantic import BaseModel, ValidationError
class User(BaseModel):
id: int
name: str
signup_ts: datetime | None = None
# 从字典创建
user = User.model_validate({'id': 123, 'name': 'James'})
print(user)
# id=123 name='James' signup_ts=None
# 验证失败
try:
User.model_validate(['not', 'a', 'dict'])
except ValidationError as e:
print(e)
# Input should be a valid dictionary or instance of User
4.2 model_validate_json():JSON 验证
# 从 JSON 字符串创建(性能更好)
m = User.model_validate_json('{"id": 123, "name": "James"}')
print(m)
# id=123 name='James' signup_ts=None
# 无效 JSON
try:
User.model_validate_json('invalid JSON')
except ValidationError as e:
print(e)
# expected value at line 1 column 1
4.3 三种验证方法对比
| 方法 | 输入类型 | 适用场景 |
|---|---|---|
model_validate() |
字典或对象 | 从 Python 数据结构创建 |
model_validate_json() |
JSON 字符串或 bytes | 从 API 响应、文件读取 |
model_validate_strings() |
字符串键值字典 | 从表单数据、环境变量 |
五、自定义校验器
5.1 @field_validator:字段级校验
当基础类型约束无法满足业务需求时,编写自定义校验器:
from pydantic import BaseModel, field_validator
import re
class User(BaseModel):
username: str
phone: str
email: str
@field_validator('username', mode='after')
@classmethod
def validate_username(cls, value: str) -> str:
"""用户名:4-20个字符,仅字母数字和下划线"""
if not re.match(r'^[a-zA-Z0-9_]{4,20}$', value):
raise ValueError('用户名必须为4-20个字母、数字或下划线')
return value
@field_validator('phone', mode='after')
@classmethod
def validate_phone(cls, value: str) -> str:
"""手机号:11位数字"""
if not re.match(r'^\d{11}$', value):
raise ValueError('手机号必须为11位数字')
return value
# 测试
try:
user = User(username="ab", phone="123", email="test@example.com")
except ValidationError as e:
print(e)
5.2 @model_validator:模型级校验
跨字段校验(如密码一致性)使用模型级校验器:
from pydantic import BaseModel, model_validator
from typing_extensions import Self
class UserRegister(BaseModel):
username: str
password: str
password_repeat: str
@model_validator(mode='after')
def check_passwords_match(self) -> Self:
if self.password != self.password_repeat:
raise ValueError('两次输入的密码不一致')
return self
# 测试
try:
user = UserRegister(
username="alice",
password="secret123",
password_repeat="secret456"
)
except ValidationError as e:
print(e)
# 1 validation error for UserRegister
# Value error, 两次输入的密码不一致
5.3 校验器模式说明
| 模式 | 说明 |
|---|---|
mode='before' |
在 Pydantic 处理前执行,接收原始输入 |
mode='after' |
在 Pydantic 处理后执行,接收已转换的值 |
mode='wrap' |
最灵活,可控制验证流程,执行前后逻辑 |
六、嵌套模型与复杂数据结构
6.1 列表与嵌套模型
真实数据往往是嵌套结构,Pydantic 支持模型嵌套:
from pydantic import BaseModel, Field
from typing import List, Literal
class Address(BaseModel):
street: str = Field(min_length=3, max_length=50)
city: str = Field(min_length=2, max_length=30)
zip_code: str = Field(pattern=r"^\d{5}$")
type: Literal["home", "work"] = Field()
class User(BaseModel):
id: int = Field(default=1, gt=0)
name: str = Field(default="someName", min_length=1)
addresses: List[Address] = Field(min_length=1) # 至少一个地址
# 创建包含嵌套模型的实例
user_data = {
"id": 1,
"name": "Alice",
"addresses": [
{
"street": "123 Main St",
"city": "New York",
"zip_code": "10001",
"type": "home"
}
]
}
user = User.model_validate(user_data)
print(user)
# id=1 name='Alice' addresses=[Address(street='123 Main St', city='New York', zip_code='10001', type='home')]
6.2 泛型模型
复用通用模型结构时,使用泛型模型:
from typing import Generic, List, TypeVar
from pydantic import BaseModel
DataT = TypeVar('DataT')
class Response(BaseModel, Generic[DataT]):
data: DataT | None = None
code: int = 200
message: str = "success"
# 不同类型的数据
print(Response[int](data=1))
# data=1 code=200 message='success'
print(Response[str](data='value'))
# data='value' code=200 message='success'
class User(BaseModel):
id: int
name: str
print(Response[User](data=User(id=1, name="Alice")))
# data=User(id=1, name='Alice') code=200 message='success'
七、实战场景
7.1 FastAPI 请求验证
FastAPI 深度集成 Pydantic,直接用模型定义请求体:
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class UserCreate(BaseModel):
username: str = Field(min_length=3, max_length=20)
email: str = Field(pattern=r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
password: str = Field(min_length=8)
@app.post("/users/")
def create_user(user: UserCreate):
# 自动验证,无需手动校验
return {"username": user.username, "email": user.email}
7.2 LLM 输出验证
在 RAG 项目中,使用 Pydantic 验证 LLM 结构化输出:
from pydantic import BaseModel, Field, field_validator
from typing import List
import json
import re
class ProductReview(BaseModel):
product_name: str
rating: int = Field(ge=1, le=5)
review_text: str
would_recommend: bool
def parse_review(llm_output: str) -> ProductReview | None:
"""解析 LLM 输出并验证"""
try:
# 提取 JSON
json_match = re.search(r'\{.*\}', llm_output, re.DOTALL)
if not json_match:
return None
data = json.loads(json_match.group())
return ProductReview(**data)
except (json.JSONDecodeError, ValidationError) as e:
print(f"解析失败: {e}")
return None
# 处理包含多余文本的 LLM 响应
messy_response = '''
Here's the review in JSON format:
{
"product_name": "Wireless Headphones X100",
"rating": 4,
"review_text": "Great sound quality, comfortable for long use.",
"would_recommend": true
}
Hope this helps!
'''
review = parse_review(messy_response)
if review:
print(f"Product: {review.product_name}")
print(f"Rating: {review.rating}/5")
7.3 配置文件管理
from pydantic import BaseModel, Field
import json
import os
class DatabaseConfig(BaseModel):
host: str = "localhost"
port: int = Field(default=5432, ge=1, le=65535)
username: str
password: str
database: str
class AppConfig(BaseModel):
app_name: str
debug: bool = False
database: DatabaseConfig
def load_config(config_path: str) -> AppConfig:
with open(config_path) as f:
data = json.load(f)
return AppConfig.model_validate(data)
# config.json
# {
# "app_name": "MyApp",
# "database": {
# "host": "localhost",
# "username": "admin",
# "password": "secret",
# "database": "mydb"
# }
# }
八、常见问题与避坑指南
8.1 问题1:参数不可哈希
lru_cache 要求参数可哈希,Pydantic 模型默认不可哈希。解决方案:
@lru_cache
def get_user_by_id(user_id: int): # ✅ 使用简单类型
return db.query(User, user_id)
# 或者将模型转为可哈希
@lru_cache
def process_user(user_tuple: tuple): # 传元组
user = User.model_validate(dict(user_tuple))
# ...
8.2 问题2:大模型无限递归
使用 model_dump() 时注意循环引用:
# ❌ 循环引用可能导致递归
user = User(name="Alice")
user.friend = user
user.model_dump() # RecursionError
# ✅ 使用 model_dump(exclude={'friend'})
8.3 问题3:性能优化
- 使用
model_validate_json()直接解析 JSON,比先解析为字典再验证更快 - 使用
model_construct()跳过验证,只用于已确认有效的数据 - 设置
ConfigDict(revalidate_instances='never')避免重复验证实例
九、总结
| 核心概念 | 说明 |
|---|---|
BaseModel |
所有 Pydantic 模型的基类 |
Field |
添加字段约束和元数据 |
@field_validator |
字段级自定义校验 |
@model_validator |
模型级跨字段校验 |
model_validate() |
从字典/对象创建并验证 |
model_validate_json() |
从 JSON 创建并验证(性能更优) |
model_dump() |
导出为字典 |
model_dump_json() |
导出为 JSON 字符串 |
什么时候用 Pydantic?
✅ 推荐使用:
- FastAPI 请求/响应验证
- 配置文件解析
- API 客户端数据解析
- LLM 输出结构化验证
- 数据库模型(配合 SQLAlchemy)
- 环境变量管理(
pydantic-settings)
❌ 不推荐:
- 极致性能场景(但 v2 已显著优化)
- 极其动态、运行时才确定的结构
扩展阅读:

浙公网安备 33010602011771号