一、前言
Pydantic 是一个 Python 数据验证 和 设置管理 库,使用 Python 类型 注解。具有以下特点:
1.1 核心功能
- 数据验证:自动验证数据类型和约束条件
- 类型转换:自动将输入数据转换为声明类型
- Schema 生成:自动生成 JSON Schema
1.2 主要优势
- 使用直观,基于类型注解
- 与 FastAPI 继承
- 优秀的错误处理机制
1.3 常见使用场景
- API 请求/响应验证
- 配置管理
- 数据序列化/反序列化
- 数据库模型验证
二、基础用法
2.1 基本模型使用
from typing import Optional, List
from datetime import datetime
from pydantic import BaseModel
class User(BaseModel):
id: int # 必填字段
name: str = "Jack" # 有默认值,选填字段
signup_ts: Optional[datetime] = None
friends: List[int] = [] # 列表元素是 int 类型 或者可以直接转化成 int 类型
external_data = {
"id": "123",
"signup_ts": "2022-12-22 12:00",
"friends": [1, 2, "3"]
}
# 实例化
user = User(**external_data)
print(user.id, user.friends)
print(repr(user))
print(user.model_dump()) # 2.0 中 dict 修改为 model_dump
"""
输出
123 [1, 2, 3]
User(id=123, name='Jack', signup_ts=datetime.datetime(2022, 12, 22, 12, 0), friends=[1, 2, 3])
{'id': 123, 'name': 'Jack', 'signup_ts': datetime.datetime(2022, 12, 22, 12, 0), 'friends': [1, 2, 3]}
"""
2.2 类型注解
from typing import Union, List, Dict
from pydantic import BaseModel, constr, Field
class Product(BaseModel):
name: constr(min_length=1, max_length=50)
price: float = Field(gt=0)
tags: List[str] = []
metadata: Dict[str, Union[str, int]] = {}
data = {
"name": "Apple",
"price": 8999,
"tags": ["时尚", "优雅"],
"metadata": {
"types": "Pro",
"price": 19999
}
}
product = Product(**data)
print(product.model_dump())
三、新特性
3.1 计算字段
@computed_field 的作用
- @computed_field 是 Pydantic V2 引入的新特性,用于声明一个字段为“计算字段”。
- 计算字段不会直接存储在模型实例中,而是根据其他字段的值动态计算。
- 在序列化时,计算字段会被包含在模型的输出中(例如,当调用 .model_dump() 或 .dict() 方法时)
from pydantic import computed_field, BaseModel
class Rectangle(BaseModel):
width: float
height: float
@computed_field
def area(self) -> float:
return self.width * self.height
width
和height
: 表示矩形的宽度和高度,类型为浮点数 (float)。area
: 使用@computed_field
装饰器定义的计算字段,表示矩形的面积。- 计算逻辑:area = width * height。
- 返回值类型为 float。
案例使用
# 创建一个矩形对象
rectangle = Rectangle(width=5.0, height=3.0)
# 访问计算字段 area
print(rectangle.area) # 输出: 15.0
# 更新矩形的宽度和高度
rectangle.width = 10.0
rectangle.height = 4.0
# 再次访问计算字段 area
print(rectangle.area) # 输出: 40.0
# 将模型转换为字典
rectangle_dict = rectangle.model_dump()
print(rectangle_dict)
# 输出:
# {'width': 10.0, 'height': 4.0, 'area': 40.0}
# 将模型转换为 JSON 格式
rectangle_json = rectangle.model_dump_json()
print(rectangle_json)
# 输出:
# {"width": 10.0, "height": 4.0, "area": 40.0}
3.2 字段验证器
- field_validator: 验证单个字段的装饰器(Pydantic V2 中使用)。
- model_validator: 验证整个模型的装饰器(Pydantic V2 中使用)。
from pydantic import field_validator, model_validator
class Order(BaseModel):
items: List[str]
total: float
@field_validator('total')
def validate_total(cls, v):
if v < 0:
raise ValueError('Total must be positive')
return v
@model_validator(mode='after')
def validate_order(self) -> 'Order':
if not self.items and self.total > 0:
raise ValueError('Cannot have total without items')
return self
- @field_validator(‘total’): 这是一个字段级别的验证器,专门验证 total 字段。
- @model_validator(mode=‘after’): 这是一个模型级别的验证器,在所有字段验证完成后执行
案例使用
# 创建一个有效的订单
order = Order(items=["item1", "item2"], total=100.0)
print(order.model_dump())
# 输出
# {'items': ['item1', 'item2'], 'total': 100.0}
验证负数 total
try:
invalid_order = Order(items=["item1"], total=-50.0)
except ValueError as e:
print(e)
# 输出:
# Total must be positive
# 验证没有商品但有总价的情况
try:
invalid_order = Order(items=[], total=50.0)
except ValueError as e:
print(e)
# 输出:
# Cannot have total without items
# 验证空订单
valid_order = Order(items=[], total=0.0)
print(valid_order)
# 输出:
# items=[] total=0.0
四、高级特性
4.1 泛型模型
from typing import Generic, TypeVar
from pydantic import BaseModel
T = TypeVar("T")
class Response(BaseModel, Generic[T]):
code: int
data: T
message: str
class User(BaseModel):
id: int
name: str
class UserResponse(Response[User]):
pass
Generic
和TypeVar
: 来自 Python 的typing
模块,用于定义泛型类型TypeVar('T')
:定义一个类型变量T
,表示可以是任意类型Generic[T]
:表示一个泛型类,允许使用类型变量T
Response
:是一个泛型类,继承了BaseModel
和Generic[T]
- 泛型参数
T
表示data
字段的类型,可以是任意类型(例如int
,str
,自定义类等) data
:表示响应的数据,类型为泛型T
案例使用
# 创建一个 User 对象
user = User(id=1, name="Alice")
# 创建一个 UserResponse 对象
response = UserResponse(code=200, data=user, message="Success")
# 打印响应对象
print(response)
# 输出:
# code=200 data=User(id=1, name='Alice') message='Success'
# 转换为字典
response_dict = response.model_dump()
print(response_dict)
# 输出:
# {'code': 200, 'data': {'id': 1, 'name': 'Alice'}, 'message': 'Success'}
# 转换为 JSON
response_json = response.model_dump_json()
print(response_json)
# 输出:
# {"code": 200, "data": {"id": 1, "name": "Alice"}, "message": "Success"}
总结
-
泛型类的作用
- 使用
Generic
和TypeVar
可以定义通用的数据模型,适用于多种数据类型
- 使用
-
适用场景
- API 响应:统一的响应格式
- 不同类型的
data
4.2 自定义类型
from pydantic import GetCoreSchemaHandler, BaseModel
from pydantic_core import core_schema
from typing_extensions import Annotated, Any
class Color:
def __init__(self, value: str):
self.value = value
@classmethod
def __get_pydantic_core_schema__(cls, _source_type: type[Any],
_handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
# 使用 Pydantic 提供的 core_schema.str_schema 方法创建验证规则
return core_schema.str_schema(
pattern="^#[0-9a-fA-F]{6}$"
)
# 定义类型别名
ColorType = Annotated[str, Color]
GetCoreSchemaHandler
: Pydantic 提供的一个工具,用于动态生成核心模式(Core Schema)。CoreSchema
: Pydantic Core 的核心模式定义,用于描述数据验证逻辑。Annotated
: 来自 typing_extensions 模块,用于为类型添加元数据或额外的行为。- get_pydantic_core_schema 是 Pydantic 的特殊方法,用于定义自定义类型的验证逻辑。
- 这里的逻辑是:
- 验证输入是否是一个字符串 (type=‘str’)。
- 字符串必须符合正则表达式 ^#[0-9a-fA-F]{6}$,即以 # 开头的 6 位十六进制颜色值(如 #FFFFFF 或 #123abc)
- 这里的逻辑是:
- 使用 Annotated 将 str 类型与 Color 类关联起来。这样,ColorType 表示一个字符串类型,但会使用 Color 类中定义的验证逻辑。
案例使用
-
在 Pydantic 模型中使用 ColorType
from pydantic import BaseModel class Product(BaseModel): name: str color: ColorType
- 定义了一个 Product 模型,其中 color 字段使用了 ColorType。
- color 字段的值会被验证为符合 # 开头的 6 位十六进制颜色值。
-
创建有效对象
# 创建一个有效的 Product 对象 product = Product(name="Laptop", color="#FFFFFF") print(product) # 输出: # name='Laptop' color='#FFFFFF'
-
验证无效颜色值
try:
invalid_product = Product(name="Laptop", color="INVALID")
except ValueError as e:
print(e)
4.3 嵌套模型
from pydantic import BaseModel
from typing import List
class Address(BaseModel):
street: str
city: str
country: str
class User(BaseModel):
name: str
address: Address
alternate_addresses: List[Address] = []
案例使用
创建 User 对象
# 创建主地址和备用地址
main_address = Address(street="123 Main St", city="New York", country="USA")
alt_address1 = Address(street="456 Elm St", city="Los Angeles", country="USA")
alt_address2 = Address(street="789 Oak St", city="Chicago", country="USA")
# 创建用户对象
user = User(
name="Alice",
address=main_address,
alternate_addresses=[alt_address1, alt_address2]
)
# 打印用户对象
print(user)
"""
输出
name='Alice' address=Address(street='123 Main St', city='New York', country='USA') alternate_addresses=[Address(street='456 Elm St', city='Los Angeles', country='USA'), Address(street='789 Oak St', city='Chicago', country='USA')]
"""
访问嵌套字段
# 访问主地址的城市
print(user.address.city) # 输出: New York
# 访问备用地址的第一个地址的街道
print(user.alternate_addresses[0].street) # 输出: 456 Elm St
序列化为字典或 JSON
user_dict = user.model_dump()
print(user_dict)
# 转换为 JSON
user_json = user.model_dump_json(indent=4)
print(user_json)
4.4 ORM 模型
from pydantic import BaseModel, constr
from typing import List
from sqlalchemy import Column, Integer, String
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.orm import declarative_base # 更新导入路径
Base = declarative_base()
# ORM 模型
class CompanyOrm(Base):
__tablename__ = "companies"
id = Column(Integer, primary_key=True, nullable=False)
public_key = Column(String(20), index=True, nullable=False, unique=True) # 修正拼写错误
name = Column(String(63), unique=True)
domains = Column(ARRAY(String(255)))
# Pydantic 模型
class CompanyMode(BaseModel):
id: int
public_key: constr(max_length=20) # 修正拼写错误
name: constr(max_length=63)
domains: List[constr(max_length=255)]
class Config:
from_attributes = True # 替代 orm_mode = True
# 测试数据
co_orm = CompanyOrm(
id=123,
public_key="footbar", # 修正拼写错误
name="Testing",
domains=["example.com", "imooc.com"]
)
# 将 ORM 对象转换为 Pydantic 模型
print(CompanyMode.model_validate(co_orm).model_dump()) # 使用 model_validate 方法
五、数据验证
5.1 基础数据验证
from pydantic import EmailStr, conint, confloat, Field
from pydantic import BaseModel
class Employee(BaseModel):
email: EmailStr
age: conint(ge=18, le=65)
salary: confloat(gt=0)
department: str = Field(pattern='^[A-Z]{2,}$')
- email: 表示员工的电子邮件地址,类型为 EmailStr,会自动验证是否符合电子邮件格式。
- age: 表示员工的年龄,类型为整数 (int),并使用 conint 约束其范围:
- ge=18: 年龄必须大于或等于 18。
- le=65: 年龄必须小于或等于 65。
- salary: 表示员工的薪水,类型为浮点数 (float),并使用 confloat 约束其范围:
- gt=0: 薪水必须大于 0。
- department: 表示员工所属部门,类型为字符串 (str),并通过 Field 添加正则表达式约束:
- pattern=‘1{2,}$’: 部门名称必须由至少两个大写字母组成。
5.2 自定义验证
from pydantic import ValidationError
class Transaction(BaseModel):
amount: float
currency: str
@field_validator('currency')
def validate_currency(cls, v):
if v not in ['USD', 'EUR', 'GBP']:
raise ValueError('Invalid currency')
return v.upper()
- @field_validator(‘currency’):
- 这是一个字段级别的验证器,专门用于验证 currency 字段。
- 如果 currency 不在允许的列表 [‘USD’, ‘EUR’, ‘GBP’] 中,则抛出 ValueError 异常,提示 “Invalid currency”。
- 如果验证通过,则将 currency 转换为大写形式并返回。
六、序列化
6.1 Json 处理
from pydantic import BaseModel
from typing import Dict, Any
from datetime import datetime
class Config(BaseModel):
name: str
data: Dict[str, Any]
class Config:
json_encoders = {
datetime: lambda v: v.isoformat()
}
# 序列化
config = Config(name="test", data={"time": datetime.now()})
json_str = config.model_dump_json()
print(json_str)
# 反序列化
config = Config.model_validate_json(json_str)
print(config)
6.2 导出选项
from pydantic import BaseModel, Field
class User(BaseModel):
id: int
password: str
email: str
class Config:
json_schema_extra = {
"example": {
"id": 1,
"password": "secret",
"email": "user@example.com"
}
}
- Config 类:
- json_schema_extra 是一个配置选项,用于在生成 JSON Schema 时添加额外的信息。
- 在这里,我们提供了一个示例数据结构(example),包含字段 id、password 和 email 的示例值。
案例使用
# 创建用户对象
user = User(id=1, password="secret", email="user@example.com")
# 打印用户对象
print(user)
# 排除字段
print(user.model_dump(exclude={'password'}))
A-Z ↩︎