Pydantic的主要用法
- Pydantic 是一个基于 Python 类型注解的数据验证和设置管理库。
- Pydantic在 AI 产品中扮演着数据验证、类型安全、序列化与配置管理的核心角色,它通过基于 Python 类型注解的声明式编程,显著提升了 AI 系统的可靠性、可维护性和开发效率。
- Pydantic在数据质量决定模型性能的 AI 领域已成为保障系统稳定性的关键工具。
1. 基础模型定义
from pydantic import BaseModel
from datetime import datetime
from typing import Optional, List
class User(BaseModel):
id: int
name: str
email: str
age: Optional[int] = None
created_at: datetime = datetime.now()
# 创建实例
user = User(id=1, name="张三", email="zhangsan@example.com")
print(user)
# 输出: id=1 name='张三' email='zhangsan@example.com' age=None created_at=datetime.datetime(...)
# 访问属性
print(user.name) # 张三
print(user.model_dump()) # 转换为字典
2. 数据验证
from pydantic import BaseModel, Field, ValidationError, EmailStr
class User(BaseModel):
name: str = Field(..., min_length=2, max_length=50, description="用户名")
age: int = Field(..., ge=0, le=150, description="年龄")
email: EmailStr # 自动验证邮箱格式
try:
# 无效数据
user = User(name="A", age=200, email="invalid-email")
except ValidationError as e:
print(e.json())
# 输出详细的验证错误信息
3. 嵌套模型
from pydantic import BaseModel
from typing import List
class Address(BaseModel):
street: str
city: str
zipcode: str
class User(BaseModel):
name: str
age: int
address: Address
tags: List[str] = []
# 创建嵌套对象
user = User(
name="李四",
age=25,
address={"street": "中山路123号", "city": "北京", "zipcode": "100000"},
tags=["python", "developer"]
)
print(user.address.city) # 北京
4. 自定义验证器
from pydantic import BaseModel, validator, field_validator
class Product(BaseModel):
name: str
price: float
discount: float = 0.0
@field_validator('price')
def price_must_be_positive(cls, v):
if v <= 0:
raise ValueError('价格必须大于0')
return v
@field_validator('discount')
def discount_valid(cls, v, info):
if v < 0 or v > 1:
raise ValueError('折扣必须在0-1之间')
return v
@property
def final_price(self) -> float:
"""计算最终价格"""
return self.price * (1 - self.discount)
# 使用
product = Product(name="笔记本电脑", price=5999.99, discount=0.1)
print(product.final_price) # 5399.991
5. 数据转换
from pydantic import BaseModel
from datetime import date
class Event(BaseModel):
name: str
date: date # 自动将字符串转换为date对象
attendees: int
# 从字典创建
data = {
"name": "Python会议",
"date": "2024-12-25",
"attendees": "100" # 字符串会自动转换为int
}
event = Event(**data)
print(event.date) # 2024-12-25 (date对象)
print(type(event.attendees)) # <class 'int'>
6. 配置和序列化
from pydantic import BaseModel
from datetime import datetime
class User(BaseModel):
id: int
name: str
password: str
class Config:
# 从ORM对象创建
from_attributes = True
# 自定义字段名
alias_generator = lambda field: field.upper()
# 排除某些字段
fields = {'password': {'exclude': True}}
# 序列化时排除密码
user = User(id=1, name="王五", password="secret123")
print(user.model_dump()) # {'id': 1, 'name': '王五'}
7. 泛型支持
from pydantic import BaseModel
from typing import Generic, TypeVar, List
T = TypeVar('T')
class APIResponse(BaseModel, Generic[T]):
code: int
message: str
data: T
# 使用
class User(BaseModel):
id: int
name: str
# 单个用户响应
response = APIResponse[User](
code=200,
message="success",
data={"id": 1, "name": "张三"}
)
# 用户列表响应
response_list = APIResponse[List[User]](
code=200,
message="success",
data=[{"id": 1, "name": "张三"}, {"id": 2, "name": "李四"}]
)
8. 实际应用场景:FastAPI集成
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
password: str = Field(..., min_length=8)
@app.post("/users/")
async def create_user(user: UserCreate):
# 自动验证请求数据
return {"message": f"用户 {user.username} 创建成功"}
9. 高级特性:递归模型
from pydantic import BaseModel
from typing import List, Optional
class Category(BaseModel):
id: int
name: str
children: List['Category'] = [] # 自引用
class Config:
# 允许自引用
arbitrary_types_allowed = True
# 创建树形结构
root = Category(
id=1,
name="电子产品",
children=[
Category(id=2, name="手机", children=[]),
Category(id=3, name="电脑", children=[
Category(id=4, name="笔记本", children=[]),
Category(id=5, name="台式机", children=[])
])
]
)
10. 环境变量支持
from pydantic_settings import BaseSettings
from typing import Optional
class Settings(BaseSettings):
app_name: str = "MyApp"
database_url: str
debug: bool = False
api_key: Optional[str] = None
class Config:
env_file = ".env" # 从.env文件读取环境变量
# 使用
settings = Settings()
print(settings.database_url) # 从环境变量或.env文件读取
Pydantic的主要优势:
- 类型安全:自动进行类型检查和转换
- 数据验证:丰富的验证器
- IDE友好:完整的类型提示支持
- 性能优异:基于Python类型提示,性能好
- 易于集成:与FastAPI等框架无缝集成
这些例子展示了Pydantic的主要特性,实际开发中可以根据需求灵活使用。
关于"6. 配置和序列化"详解
Config基础概念
在Pydantic中,Config类是用来配置模型行为的特殊内部类。
1. 基本模型
from pydantic import BaseModel
from datetime import datetime
class User(BaseModel):
id: int
name: str
password: str
# 创建用户
user = User(id=1, name="王五", password="secret123")
# 默认序列化:所有字段都会输出
print(user.model_dump())
# 输出: {'id': 1, 'name': '王五', 'password': 'secret123'}
2. 添加Config配置
class User(BaseModel):
id: int
name: str
password: str
class Config:
# 1. from_attributes = True
from_attributes = True
# 2. alias_generator = lambda field: field.upper()
alias_generator = lambda field: field.upper()
# 3. fields = {'password': {'exclude': True}}
fields = {'password': {'exclude': True}}
现在我来详细解释每个配置项:
配置1:from_attributes = True
这个配置允许从其他对象创建Pydantic模型实例,比如从数据库ORM对象:
# 假设有一个SQLAlchemy的User模型
class SQLAlchemyUser:
def __init__(self, id, name, password):
self.id = id
self.name = name
self.password = password
# 创建ORM对象
db_user = SQLAlchemyUser(id=1, name="赵六", password="pass456")
# 没有from_attributes = True时,这样会报错
# user = User.model_validate(db_user) # 错误!
# 有了from_attributes = True,就可以直接从ORM对象创建
user = User.model_validate(db_user) # 正确!
print(user.name) # 输出: 赵六
实际应用场景:
# 从数据库查询出来的对象
db_user = db.query(User).first() # 这是一个SQLAlchemy对象
# 直接转换为Pydantic模型用于API响应
pydantic_user = User.model_validate(db_user)
配置2:alias_generator = lambda field: field.upper()
这个配置定义了字段别名生成规则。upper()表示将字段名转换为大写:
class User(BaseModel):
id: int
name: str
password: str
class Config:
alias_generator = lambda field: field.upper()
# 创建对象时可以使用大写字段名
user = User(ID=1, NAME="王五", PASSWORD="secret123")
# 虽然字段名是小写的,但可以用大写传入
print(user.id) # 1
print(user.name) # 王五
# 序列化时也会使用别名
print(user.model_dump(by_alias=True))
# 输出: {'ID': 1, 'NAME': '王五', 'PASSWORD': 'secret123'}
print(user.model_dump())
# 输出: {'id': 1, 'name': '王五', 'password': 'secret123'} # 默认还是原字段名
实际应用场景:处理不同命名规范的API(例如,Python使用小写,但API使用大写或驼峰)
# 处理使用驼峰命名的API
class User(BaseModel):
user_id: int
user_name: str
class Config:
alias_generator = lambda field: ''.join(
word.capitalize() if i > 0 else word
for i, word in enumerate(field.split('_'))
) # 转换为驼峰: user_name -> userName
# 现在可以接收驼峰格式的数据
user = User(userId=1, userName="张三") # 使用驼峰
参考:关于驼峰命名的API处理详解
配置3:fields = {'password': {'exclude': True}}
这个配置指定在序列化时排除某些字段:
class User(BaseModel):
id: int
name: str
password: str
class Config:
fields = {'password': {'exclude': True}}
user = User(id=1, name="王五", password="secret123")
# 序列化时自动排除password字段
print(user.model_dump())
# 输出: {'id': 1, 'name': '王五'}
# 注意:password被排除了
# 即使想包含password也包含不了
print(user.model_dump(exclude={'password': False})) # 依然没有password
完整示例对比
from pydantic import BaseModel
# 无配置版本
class UserSimple(BaseModel):
id: int
name: str
password: str
# 完整配置版本
class UserConfigured(BaseModel):
id: int
name: str
password: str
class Config:
from_attributes = True
alias_generator = lambda field: field.upper()
fields = {'password': {'exclude': True}}
# 演示对比
print("=== 简单版本 ===")
user1 = UserSimple(id=1, name="张三", password="123")
print(user1.model_dump()) # {'id': 1, 'name': '张三', 'password': '123'}
print("\n=== 配置版本 ===")
# 1. 可以使用大写别名
user2 = UserConfigured(ID=1, NAME="李四", PASSWORD="456")
print(user2.name) # 李四
# 2. 序列化时排除password
print(user2.model_dump()) # {'id': 1, 'name': '李四'}
# 3. 可以输出带别名的版本
print(user2.model_dump(by_alias=True)) # {'ID': 1, 'NAME': '李四'}
print("\n=== 从ORM对象创建 ===")
class ORMUser:
def __init__(self, id, name, password):
self.id = id
self.name = name
self.password = password
# 从ORM对象直接创建
orm_user = ORMUser(id=3, name="王五", password="789")
pydantic_user = UserConfigured.model_validate(orm_user)
print(pydantic_user.model_dump()) # {'id': 3, 'name': '王五'}
# password字段被自动排除
其他常用Config配置
class User(BaseModel):
id: int
name: str
email: str
class Config:
# 1. 允许额外字段
extra = "allow" # 或 "forbid"(禁止)或 "ignore"(忽略)
# 2. 设置JSON编码器
json_encoders = {
datetime: lambda v: v.isoformat()
}
# 3. 验证赋值
validate_assignment = True # 赋值时重新验证
# 4. 标题
title = "User Model"
总结
这个示例展示了Pydantic的三个重要配置:
from_attributes = True:让模型可以从其他Python对象(如数据库ORM对象)创建alias_generator:自动生成字段别名,用于处理不同命名规范的输入输出fields = {'password': {'exclude': True}}:永久排除敏感字段(如密码),防止意外泄露
这些配置让Pydantic模型更加灵活和安全,特别适合在实际项目中使用。
关于驼峰命名API处理详解
# 处理使用驼峰命名的API
class User(BaseModel):
user_id: int
user_name: str
class Config:
alias_generator = lambda field: ''.join(
word.capitalize() if i > 0 else word
for i, word in enumerate(field.split('_'))
) # 转换为驼峰: user_name -> userName
# 现在可以接收驼峰格式的数据
user = User(userId=1, userName="张三") # 使用驼峰
这段代码的详细解释
这段代码解决了一个常见但棘手的问题:Python 后端使用蛇形命名(snake_case),而外部 API 使用驼峰命名(camelCase)时的自动转换。
1. 问题背景
命名规范的冲突
# Python 惯例:蛇形命名 (snake_case)
user_id = 1
user_name = "张三"
is_active = True
# JavaScript/JSON API 惯例:驼峰命名 (camelCase)
{
"userId": 1,
"userName": "张三",
"isActive": true
}
直接使用的痛苦
# ❌ 如果没有转换,必须手动处理
class User(BaseModel):
user_id: int
user_name: str
# API 返回的是驼峰格式
api_response = {"userId": 1, "userName": "张三"}
# 需要手动转换
user = User(
user_id=api_response["userId"],
user_name=api_response["userName"]
)
# 或者先转换字典键
converted = {
"user_id": api_response["userId"],
"user_name": api_response["userName"]
}
user = User.model_validate(converted)
# 很繁琐!每个字段都要处理
2. 代码逐行解析
类定义
class User(BaseModel):
user_id: int
user_name: str
作用:定义 Pydantic 模型,使用 Python 标准的蛇形命名。
Config 类配置
class Config:
alias_generator = lambda field: ''.join(
word.capitalize() if i > 0 else word
for i, word in enumerate(field.split('_'))
)
这是核心部分,我们分解来看:
2.1 alias_generator
alias_generator = lambda field: ...
- 作用:自动为每个字段生成别名(alias)
- 输入:字段名(如
"user_id") - 输出:别名(如
"userId") - 效果:Pydantic 会自动设置
alias="userId"等价于手动写:
class User(BaseModel):
user_id: int = Field(alias="userId")
user_name: str = Field(alias="userName")
2.2 Lambda 函数分解
lambda field: ''.join(
word.capitalize() if i > 0 else word
for i, word in enumerate(field.split('_'))
)
执行过程示例:输入 "user_id"
步骤 1: field.split('_')
"user_id".split('_') # 结果: ['user', 'id']
步骤 2: enumerate() 添加索引
enumerate(['user', 'id'])
# 结果: [(0, 'user'), (1, 'id')]
步骤 3: 列表推导式处理每个单词
for i, word in enumerate(['user', 'id']):
# i=0, word='user' -> 保持原样: 'user'
# i=1, word='id' -> 首字母大写: 'Id'
word.capitalize() if i > 0 else word
步骤 4: ''.join() 拼接
''.join(['user', 'Id']) # 结果: 'userId'
2.3 更多转换示例
# 测试不同的字段名
fields = [
"user_id", # -> "userId"
"user_name", # -> "userName"
"first_name", # -> "firstName"
"is_active", # -> "isActive"
"created_at", # -> "createdAt"
"order_item_id" # -> "orderItemId"
]
for field in fields:
result = ''.join(
word.capitalize() if i > 0 else word
for i, word in enumerate(field.split('_'))
)
print(f"{field:15} -> {result}")
输出:
user_id -> userId
user_name -> userName
first_name -> firstName
is_active -> isActive
created_at -> createdAt
order_item_id -> orderItemId
3. 完整使用示例
3.1 创建实例(使用别名)
# 现在可以使用驼峰命名创建实例
user = User(userId=1, userName="张三")
# 访问属性仍然使用蛇形
print(user.user_id) # 1
print(user.user_name) # 张三
# 查看内部数据
print(user.model_dump())
# {'user_id': 1, 'user_name': '张三'}
# 查看带别名的输出
print(user.model_dump(by_alias=True))
# {'userId': 1, 'userName': '张三'}
3.2 验证数据(接收 API 响应)
# API 返回的驼峰格式数据
api_response = {
"userId": 1,
"userName": "张三"
}
# 自动识别别名,无需手动转换
user = User.model_validate(api_response)
print(user.user_name) # 张三
# 也可以使用 model_validate_json
json_str = '{"userId": 1, "userName": "张三"}'
user = User.model_validate_json(json_str)
print(user.user_id) # 1
3.3 输出数据(发送 API 请求)
user = User(user_id=1, user_name="张三")
# 发送给 API 时自动转为驼峰
request_body = user.model_dump(by_alias=True)
print(request_body) # {'userId': 1, 'userName': '张三'}
# 直接转换为 JSON
json_data = user.model_dump_json(by_alias=True)
print(json_data) # '{"userId": 1, "userName": "张三"}'
4. 进阶配置选项
4.1 双向转换(推荐)
class User(BaseModel):
user_id: int
user_name: str
class Config:
# 生成别名
alias_generator = lambda field: ''.join(
word.capitalize() if i > 0 else word
for i, word in enumerate(field.split('_'))
)
# 允许使用别名作为参数名
populate_by_name = True
效果:
# 两种方式都可以创建实例
user1 = User(user_id=1, user_name="张三") # 蛇形
user2 = User(userId=2, userName="李四") # 驼峰
user3 = User(**api_response) # 自动识别
4.2 自定义转换函数
def snake_to_camel(field: str) -> str:
"""蛇形转驼峰"""
parts = field.split('_')
return parts[0] + ''.join(p.capitalize() for p in parts[1:])
def camel_to_snake(field: str) -> str:
"""驼峰转蛇形(用于反向转换)"""
import re
return re.sub(r'(?<!^)(?=[A-Z])', '_', field).lower()
class User(BaseModel):
user_id: int
user_name: str
class Config:
alias_generator = snake_to_camel
populate_by_name = True
5. 实际应用场景
场景 1: FastAPI 端点
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class UserCreate(BaseModel):
user_id: int
user_name: str
email_address: str
class Config:
alias_generator = lambda f: ''.join(
w.capitalize() if i > 0 else w
for i, w in enumerate(f.split('_'))
)
populate_by_name = True
@app.post("/users")
async def create_user(user: UserCreate):
# 前端发送: {"userId": 1, "userName": "张三", "emailAddress": "test@example.com"}
# FastAPI 自动解析为 UserCreate 实例
# 内部使用蛇形访问
print(f"Creating user: {user.user_name}")
# 返回时自动转回驼峰
return user.model_dump(by_alias=True)
场景 2: 调用外部 API
import httpx
from pydantic import BaseModel
class ExternalAPIResponse(BaseModel):
order_id: int
customer_name: str
total_amount: float
class Config:
alias_generator = lambda f: ''.join(
w.capitalize() if i > 0 else w
for i, w in enumerate(f.split('_'))
)
populate_by_name = True
async def fetch_order(order_id: int):
async with httpx.AsyncClient() as client:
# 外部 API 返回驼峰格式
response = await client.get(f"https://api.example.com/orders/{order_id}")
# 自动转换!无需手动处理字段名
order = ExternalAPIResponse.model_validate(response.json())
# 使用蛇形属性
print(f"Order {order.order_id}: {order.customer_name}")
return order
场景 3: 数据库模型转换
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
# 数据库模型(蛇形)
class UserDB(Base):
__tablename__ = "users"
user_id = Column(Integer, primary_key=True)
user_name = Column(String)
created_at = Column(DateTime)
# API 模型(支持驼峰)
class UserAPI(BaseModel):
user_id: int
user_name: str
created_at: datetime
class Config:
alias_generator = lambda f: ''.join(
w.capitalize() if i > 0 else w
for i, w in enumerate(f.split('_'))
)
populate_by_name = True
# 从数据库读取,转换为 API 格式
db_user = session.query(UserDB).first()
api_user = UserAPI.model_validate(db_user.__dict__)
response = api_user.model_dump(by_alias=True) # 驼峰格式返回给前端
6. 注意事项和最佳实践
⚠️ 注意 1: 缩写处理
# 缩写可能不符合预期
"user_id" -> "userId" # ✅ 正确
"api_key" -> "apiKey" # ✅ 正确
"user_io" -> "userIo" # ⚠️ "IO" 通常应保持大写 "userIO"
"xml_data" -> "xmlData" # ⚠️ "XML" 通常应保持大写 "XMLData"
# 改进版:处理常见缩写
def smart_snake_to_camel(field: str) -> str:
abbreviations = {'id': 'ID', 'api': 'API', 'xml': 'XML', 'json': 'JSON'}
parts = field.split('_')
result = [parts[0]]
for part in parts[1:]:
if part in abbreviations:
result.append(abbreviations[part])
else:
result.append(part.capitalize())
return ''.join(result)
# 测试
print(smart_snake_to_camel("api_key")) # APIKey
print(smart_snake_to_camel("user_id")) # userID
print(smart_snake_to_camel("xml_data")) # XMLData
⚠️ 注意 2: 嵌套模型
# 嵌套模型也需要转换
class Address(BaseModel):
street_name: str
postal_code: str
class Config:
alias_generator = lambda f: ''.join(
w.capitalize() if i > 0 else w
for i, w in enumerate(f.split('_'))
)
populate_by_name = True
class User(BaseModel):
user_name: str
address: Address # 嵌套模型会自动处理
class Config:
alias_generator = lambda f: ''.join(
w.capitalize() if i > 0 else w
for i, w in enumerate(f.split('_'))
)
populate_by_name = True
# 完美处理嵌套驼峰
data = {
"userName": "张三",
"address": {
"streetName": "南京路",
"postalCode": "200000"
}
}
user = User.model_validate(data)
print(user.address.street_name) # 南京路
⚠️ 注意 3: Pydantic V2 语法
# Pydantic V2 推荐使用 model_config
from pydantic import BaseModel, ConfigDict
class User(BaseModel):
user_id: int
user_name: str
model_config = ConfigDict(
alias_generator=lambda f: ''.join(
word.capitalize() if i > 0 else word
for i, word in enumerate(f.split('_'))
),
populate_by_name=True
)
7. 总结
| 方面 | 说明 |
|---|---|
| 核心价值 | 自动转换 Python 蛇形命名和 API 驼峰命名 |
| 工作原理 | alias_generator 自动为每个字段生成别名 |
| 使用方式 | 创建实例时可用驼峰,访问时用蛇形 |
| 输出控制 | by_alias=True 控制输出格式 |
| 最佳实践 | 配合 populate_by_name=True 实现双向兼容 |
这段代码的本质是:在 Python 内部保持清晰的蛇形命名规范,在与外部系统交互时自动转换为驼峰格式,避免手动处理字段名映射的繁琐和错误。
补充:示例中V1代码在V2环境下可能执行出错,为此补充如下V1和V2的差异
Pydantic V1 vs V2 主要区别
Pydantic V2 是一次完全重写,在性能、API设计和功能上都有重大改进。V2 引入了许多不兼容的更改,但也带来了显著的性能提升和新特性。以下是详细对比:
核心差异速览表
| 对比维度 | Pydantic V1 | Pydantic V2 |
|---|---|---|
| 性能 | 基准性能 | 快 5-50倍(完全重写的核心) |
| 基类 | pydantic.BaseModel | 同,但方法名全部统一 |
| 泛型 | pydantic.generics.GenericModel | 直接使用 BaseModel, Generic[T] |
| ORM模式 | orm_mode = True | from_attributes = True |
| 根模型 | __root__ 字段 | RootModel 类型 |
| 配置 | class Config 类 | model_config = ConfigDict(...) |
1. 方法名称变更(最直接的改动)
V2 统一了命名规范,所有主要方法都采用 model_* 前缀:
| 功能 | V1 方法 | V2 方法 |
|---|---|---|
| 获取字段 | __fields__ | model_fields |
| 构造实例 | construct() | model_construct() |
| 复制 | copy() | model_copy() |
| 转字典 | dict() | model_dump() |
| 转JSON | json() | model_dump_json() |
| 从对象解析 | parse_obj() | model_validate() |
| 从JSON解析 | parse_raw() | model_validate_json() |
| JSON Schema | schema() | model_json_schema() |
| 更新引用 | update_forward_refs() | model_rebuild() |
代码示例对比
# V1 写法
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str
user = User(id=1, name="张三")
print(user.dict()) # {'id': 1, 'name': '张三'}
print(user.json()) # {"id": 1, "name": "张三"}
# V2 写法
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str
user = User(id=1, name="张三")
print(user.model_dump()) # {'id': 1, 'name': '张三'}
print(user.model_dump_json()) # {"id":1,"name":"张三"}
2. 泛型模型(GenericModel 移除)
V1 需要专门的 GenericModel 类,V2 直接继承 BaseModel 和 Generic:
# V1 写法
from pydantic.generics import GenericModel
from typing import Generic, TypeVar
T = TypeVar('T')
class Response(GenericModel, Generic[T]):
data: T
code: int
# V2 写法
from pydantic import BaseModel
from typing import Generic, TypeVar
T = TypeVar('T')
class Response(BaseModel, Generic[T]):
data: T
code: int
3. ORM模式配置变更
# V1
class User(BaseModel):
id: int
name: str
class Config:
orm_mode = True # 从ORM对象读取
# V2
class User(BaseModel):
id: int
name: str
class Config:
from_attributes = True # 新的配置名
使用方式也变了:
# V1: 使用 from_orm()
user = User.from_orm(db_user)
# V2: 使用 model_validate() + from_attributes=True
user = User.model_validate(db_user) # 需要 Config 中设置 from_attributes=True
4. 自定义根模型(RootModel)
V1 使用 __root__ 字段,V2 引入专门的 RootModel 类型:
# V1 写法
class Data(BaseModel):
__root__: List[int]
data = Data(__root__=[1, 2, 3])
print(data.__root__) # [1, 2, 3]
# V2 写法
from pydantic import RootModel
Data = RootModel[List[int]]
data = Data([1, 2, 3])
print(data.root) # [1, 2, 3]
5. Field 字段约束变更
多个字段参数被重命名或移除:
| V1 参数 | V2 参数 | 说明 |
|---|---|---|
min_items | min_length | 列表最小长度 |
max_items | max_length | 列表最大长度 |
regex | pattern | 正则表达式 |
allow_mutation | frozen | 不可变(逻辑反转) |
const | 移除 | 使用 Literal 类型 |
unique_items | 移除 | 不再支持 |
示例
# V1
class Model(BaseModel):
name: str = Field(..., regex="^[A-Z]")
# V2
class Model(BaseModel):
name: str = Field(..., pattern="^[A-Z]")
6. JSON Schema 自定义
V2 将 JSON Schema 的额外数据统一到 json_schema_extra 参数:
# V1
class Model(BaseModel):
name: str = Field(..., title="姓名", description="用户姓名")
class Config:
schema_extra = {"examples": [{"name": "张三"}]}
# V2
class Model(BaseModel):
name: str = Field(..., title="姓名", description="用户姓名")
class Config:
json_schema_extra = {"examples": [{"name": "张三"}]}
7. 序列化器(重要新功能)
V2 提供了更灵活的序列化控制:
from pydantic import BaseModel, field_serializer, model_serializer
class User(BaseModel):
name: str
password: str
# 字段级序列化器
@field_serializer('password')
def hide_password(self, password: str) -> str:
return '***'
# 模型级序列化器
@model_serializer
def custom_serialize(self):
return {k: v for k, v in self.__dict__.items() if k != 'password'}
user = User(name="张三", password="secret")
print(user.model_dump()) # {'name': '张三', 'password': '***'}
8. 配置系统升级
V2 使用 ConfigDict 替代原有的 Config 类,提供更好的类型提示:
from pydantic import BaseModel, ConfigDict
class User(BaseModel):
id: int
name: str
model_config = ConfigDict(
from_attributes=True,
extra='forbid',
frozen=False,
str_strip_whitespace=True
)
9. 新功能亮点(V2独有)
Python 3.14 支持
V2.12+ 支持 Python 3.14 的延迟类型注解评估(PEP 649)
MISSING 哨兵(实验性)
区分"未提供"和"None"值:
from pydantic import BaseModel
from pydantic.experimental.missing_sentinel import MISSING
class Config(BaseModel):
timeout: int | None | MISSING = MISSING
排除条件字段
from pydantic import BaseModel, Field
class Transaction(BaseModel):
id: int
value: int = Field(ge=0, exclude_if=lambda v: v == 0)
时间戳单位控制
from pydantic import BaseModel, ConfigDict
from datetime import datetime
class Model(BaseModel):
d: datetime
model_config = ConfigDict(val_temporal_unit='milliseconds')
10. 向后兼容策略
V2 提供了 pydantic.v1 模块以兼容旧代码:
# 仍然可以使用 V1 API
from pydantic.v1 import BaseModel # V1 风格
class User(BaseModel):
name: str
V1 将在 2024年6月30日后停止安全更新。
升级建议
-
使用自动迁移工具
bump-pydantic:pip install bump-pydantic bump-pydantic your_package/ -
逐步迁移:利用
pydantic.v1命名空间分模块升级 -
重点检查:
- 所有
.dict()/.json()调用 orm_mode配置__root__根模型GenericModel泛型- 自定义 Field 约束参数
- 所有
V2 虽然改动较大,但性能提升和 API 统一让长期维护更加轻松。