Pydantic的主要用法

80 阅读17分钟

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的主要优势:

  1. 类型安全:自动进行类型检查和转换
  2. 数据验证:丰富的验证器
  3. IDE友好:完整的类型提示支持
  4. 性能优异:基于Python类型提示,性能好
  5. 易于集成:与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的三个重要配置:

  1. from_attributes = True:让模型可以从其他Python对象(如数据库ORM对象)创建
  2. alias_generator:自动生成字段别名,用于处理不同命名规范的输入输出
  3. 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 V1Pydantic V2
性能基准性能5-50倍(完全重写的核心)
基类pydantic.BaseModel同,但方法名全部统一
泛型pydantic.generics.GenericModel直接使用 BaseModel, Generic[T]
ORM模式orm_mode = Truefrom_attributes = True
根模型__root__ 字段RootModel 类型
配置class Configmodel_config = ConfigDict(...)

1. 方法名称变更(最直接的改动)

V2 统一了命名规范,所有主要方法都采用 model_* 前缀:

功能V1 方法V2 方法
获取字段__fields__model_fields
构造实例construct()model_construct()
复制copy()model_copy()
转字典dict()model_dump()
转JSONjson()model_dump_json()
从对象解析parse_obj()model_validate()
从JSON解析parse_raw()model_validate_json()
JSON Schemaschema()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 直接继承 BaseModelGeneric

# 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_itemsmin_length列表最小长度
max_itemsmax_length列表最大长度
regexpattern正则表达式
allow_mutationfrozen不可变(逻辑反转)
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日后停止安全更新。


升级建议

  1. 使用自动迁移工具 bump-pydantic

    pip install bump-pydantic
    bump-pydantic your_package/
    
  2. 逐步迁移:利用 pydantic.v1 命名空间分模块升级

  3. 重点检查

    • 所有 .dict() / .json() 调用
    • orm_mode 配置
    • __root__ 根模型
    • GenericModel 泛型
    • 自定义 Field 约束参数

V2 虽然改动较大,但性能提升和 API 统一让长期维护更加轻松。