Pydantic 是一个 Python 库,主要用于数据验证和设置管理,使用 Python 类型注解。它在 FastAPI 等现代 Python 框架中被广泛使用,因其强大的数据验证能力和简洁的 API 设计而备受开发者青睐。
1. 核心概念 #
1.1 基本模型 #
Pydantic 的核心是模型(Model),通过继承 pydantic.BaseModel 来定义数据结构。模型使用 Python 类型注解来定义字段类型,Pydantic 会自动验证输入数据是否符合这些类型定义,并进行必要的类型转换。
# 导入必要的模块
from pydantic import BaseModel
from datetime import datetime
# 定义一个用户模型,继承自BaseModel
class User(BaseModel):
# 用户ID,必填字段,整数类型
id: int
# 用户名,可选字段,字符串类型,默认值为"John Doe"
name: str = "John Doe"
# 注册时间,可选字段,datetime类型或None,默认值为None
signup_ts: datetime | None = None
# 朋友列表,可选字段,整数列表,默认值为空列表
friends: list[int] = []
# 测试代码
if __name__ == "__main__":
# 创建用户实例
user = User(id=1, name="Alice")
print(f"用户: {user.name}, ID: {user.id}")
print(f"朋友列表: {user.friends}")1.2 主要特点 #
Pydantic 的主要特点包括类型注解、自动验证、数据转换、易用性和高性能。这些特点使得 Pydantic 成为现代 Python 开发中数据验证的首选工具。
- 类型注解:使用 Python 类型提示
- 自动验证:自动验证输入数据是否符合类型注解
- 数据转换:自动将输入数据转换为正确的类型
- 易于使用:与 Python 生态系统无缝集成
- 高性能:核心逻辑用 Rust 实现
2. 基本用法 #
2.1 模型实例化 #
模型实例化是 Pydantic 的基本用法,可以从字典、JSON 数据或其他格式创建模型实例。Pydantic 会自动验证和转换数据类型,确保数据符合模型定义。
# 导入必要的模块
from pydantic import BaseModel
from datetime import datetime
# 定义用户模型
class User(BaseModel):
# 用户ID,整数类型
id: int
# 用户名,字符串类型,默认值
name: str = "John Doe"
# 注册时间,datetime类型或None,默认值
signup_ts: datetime | None = None
# 朋友列表,整数列表,默认值
friends: list[int] = []
# 外部数据(可能是从API或数据库获取的)
external_data = {
"id": "123", # 字符串会自动转换为整数
"signup_ts": "2023-01-01 12:22",
"friends": [1, 2, "3"] # 字符串会自动转换为整数
}
# 从外部数据创建用户实例
user = User(**external_data)
# 打印用户ID(已转换为整数)
print(user.id) # 123 (整数)
# 打印注册时间(已转换为datetime对象)
print(repr(user.signup_ts)) # datetime.datetime(2023, 1, 1, 12, 22)
# 打印朋友列表(已转换为整数列表)
print(user.friends) # [1, 2, 3]
# 测试代码
if __name__ == "__main__":
print(f"用户ID类型: {type(user.id)}")
print(f"注册时间类型: {type(user.signup_ts)}")
print(f"朋友列表类型: {type(user.friends)}")2.2 模型导出 #
Pydantic 模型可以方便地导出为字典或 JSON 格式,这对于 API 响应、数据序列化和存储非常有用。model_dump() 方法将模型转换为字典,model_dump_json() 方法将模型转换为 JSON 字符串。
# 导入必要的模块
from pydantic import BaseModel
from datetime import datetime
# 定义用户模型
class User(BaseModel):
# 用户ID,整数类型
id: int
# 用户名,字符串类型
name: str = "John Doe"
# 注册时间,datetime类型或None
signup_ts: datetime | None = None
# 朋友列表,整数列表
friends: list[int] = []
# 创建用户实例
user = User(id=123, name="Alice", friends=[1, 2, 3])
# 转换为字典
user_dict = user.model_dump()
print("模型转字典:")
print(user_dict)
# 转换为JSON字符串
user_json = user.model_dump_json()
print("\n模型转JSON:")
print(user_json)
# 测试代码
if __name__ == "__main__":
print(f"字典类型: {type(user_dict)}")
print(f"JSON类型: {type(user_json)}")2.3 验证错误处理 #
当输入数据不符合模型定义时,Pydantic 会抛出 ValidationError 异常。这个异常包含详细的错误信息,包括错误类型、位置和具体问题,有助于调试和数据验证。
# 导入必要的模块
from pydantic import BaseModel, ValidationError
from datetime import datetime
# 定义用户模型
class User(BaseModel):
# 用户ID,整数类型
id: int
# 用户名,字符串类型
name: str = "John Doe"
# 注册时间,datetime类型或None
signup_ts: datetime | None = None
# 朋友列表,整数列表
friends: list[int] = []
# 测试验证错误处理
if __name__ == "__main__":
try:
# 尝试创建包含无效数据的用户实例
User(id="not an int", friends=["not number"])
except ValidationError as e:
# 打印详细的错误信息
print("验证错误详情:")
print(e.errors())
"""
输出示例:
[
{
'type': 'int_parsing',
'loc': ('id',),
'msg': 'Input should be a valid integer, unable to parse string as an integer',
'input': 'not an int',
},
{
'type': 'int_parsing',
'loc': ('friends', 0),
'msg': 'Input should be a valid integer, unable to parse string as an integer',
'input': 'not number',
}
]
"""3. 高级特性 #
3.1 字段类型 #
Pydantic 支持丰富的字段类型,包括基本类型、特殊字符串类型、复杂类型和自定义类型。这些类型提供了强大的数据验证和转换能力。
# 导入必要的模块
from pydantic import BaseModel, EmailStr, HttpUrl, Field
from typing import Literal, Union
from datetime import datetime
from uuid import UUID
# 定义高级模型,展示各种字段类型
class AdvancedModel(BaseModel):
# 基本类型
name: str
age: int
# 特殊字符串类型 - 邮箱验证
email: EmailStr
# 特殊字符串类型 - URL验证
website: HttpUrl
# 复杂类型 - UUID
uuid: UUID
# 复杂类型 - 时间戳
timestamp: datetime
# 联合类型 - 可以是整数或字符串
value: Union[int, str]
# 字面量类型 - 只能是"active"或"inactive"
status: Literal["active", "inactive"] = "active"
# 字段配置 - 密码字段,长度8-32,只允许字母数字
password: str = Field(..., min_length=8, max_length=32, regex="[A-Za-z0-9]+")
# 测试代码
if __name__ == "__main__":
try:
# 创建高级模型实例
model = AdvancedModel(
name="Test User",
age=25,
email="test@example.com",
website="https://example.com",
uuid="123e4567-e89b-12d3-a456-426614174000",
timestamp="2023-01-01T12:00:00",
value=42,
password="secure123"
)
print("模型创建成功!")
print(f"状态: {model.status}")
except Exception as e:
print(f"创建失败: {e}")3.2 验证器 #
Pydantic 允许使用 @validator 装饰器定义自定义验证逻辑。验证器可以检查单个字段或多个字段之间的关系,提供灵活的数据验证能力。
# 导入必要的模块
from pydantic import BaseModel, validator
# 定义用户模型,包含自定义验证器
class UserModel(BaseModel):
# 用户名,字符串类型
username: str
# 密码1,字符串类型
password1: str
# 密码2,字符串类型
password2: str
# 验证用户名必须包含字母
@validator("username")
def username_must_contain_letter(cls, v):
# 检查是否包含字母
if not any(c.isalpha() for c in v):
raise ValueError("必须包含字母")
return v
# 验证两个密码是否匹配
@validator("password2")
def passwords_match(cls, v, values, **kwargs):
# 检查password1是否在values中,且与password2不匹配
if "password1" in values and v != values["password1"]:
raise ValueError("密码不匹配")
return v
# 测试代码
if __name__ == "__main__":
try:
# 测试有效数据
user = UserModel(username="user123", password1="pass123", password2="pass123")
print("用户创建成功!")
except Exception as e:
print(f"创建失败: {e}")
try:
# 测试无效用户名(不包含字母)
user = UserModel(username="123", password1="pass123", password2="pass123")
except Exception as e:
print(f"用户名验证失败: {e}")
try:
# 测试密码不匹配
user = UserModel(username="user123", password1="pass123", password2="different")
except Exception as e:
print(f"密码验证失败: {e}")3.3 模型配置 #
Pydantic 模型可以通过 model_config 进行配置,控制模型的行为,如是否允许额外字段、是否使模型不可变、是否添加示例等。
# 导入必要的模块
from pydantic import BaseModel
# 定义配置模型,展示各种配置选项
class ConfigModel(BaseModel):
# 模型配置字典
model_config = {
"extra": "forbid", # 禁止额外字段
"frozen": True, # 使模型不可变
"json_schema_extra": {
"examples": [{
"name": "Example",
"age": 25
}]
}
}
# 模型字段
name: str
age: int
# 测试代码
if __name__ == "__main__":
try:
# 创建配置模型实例
model = ConfigModel(name="Test", age=25)
print("模型创建成功!")
print(f"名称: {model.name}, 年龄: {model.age}")
# 尝试修改字段(应该失败,因为frozen=True)
try:
model.name = "New Name"
except Exception as e:
print(f"修改字段失败(预期): {e}")
except Exception as e:
print(f"创建失败: {e}")3.4 嵌套模型 #
Pydantic 支持嵌套模型,可以在一个模型中包含其他模型。这对于表示复杂的数据结构非常有用,如用户包含多个地址等。
# 导入必要的模块
from typing import List
from pydantic import BaseModel
# 定义地址模型
class Address(BaseModel):
# 街道地址
street: str
# 城市
city: str
# 邮政编码
zip_code: str
# 定义用户模型,包含地址列表
class User(BaseModel):
# 用户名
name: str
# 地址列表
addresses: List[Address]
# 测试代码
if __name__ == "__main__":
# 创建用户实例,包含多个地址
user = User(
name="John",
addresses=[
{"street": "123 Main St", "city": "Anytown", "zip_code": "12345"},
{"street": "456 Oak Ave", "city": "Somewhere", "zip_code": "67890"}
]
)
print(f"用户: {user.name}")
print("地址列表:")
for i, addr in enumerate(user.addresses, 1):
print(f" 地址{i}: {addr.street}, {addr.city} {addr.zip_code}")4. 实用功能 #
4.1 动态模型创建 #
Pydantic 支持动态创建模型,这在需要根据运行时条件创建不同数据结构的场景中非常有用。create_model 函数允许在运行时创建模型类。
# 导入必要的模块
from pydantic import BaseModel, create_model
# 动态创建模型
DynamicModel = create_model(
"DynamicModel",
name=(str, ...), # 必填字段,字符串类型
age=(int, 18), # 可选字段,整数类型,默认值18
)
# 测试代码
if __name__ == "__main__":
# 创建动态模型实例
obj = DynamicModel(name="Alice")
print(f"动态模型: {obj}") # name='Alice' age=18
# 创建另一个实例,指定年龄
obj2 = DynamicModel(name="Bob", age=25)
print(f"动态模型2: {obj2}") # name='Bob' age=254.2 数据解析 #
Pydantic 提供了强大的数据解析功能,可以将各种格式的数据转换为模型实例。parse_obj_as 用于解析对象,parse_raw_as 用于解析原始数据如 JSON 字符串。
# 导入必要的模块
from pydantic import BaseModel, parse_obj_as, parse_raw_as
# 定义用户模型
class User(BaseModel):
# 用户名
name: str
# 年龄,可选
age: int = 18
# 测试代码
if __name__ == "__main__":
# 解析对象列表
users = parse_obj_as(list[User], [{"name": "John"}, {"name": "Alice"}])
print("解析对象列表:")
for user in users:
print(f" {user.name} (年龄: {user.age})")
# 解析JSON字符串
users_json = parse_raw_as(list[User], '[{"name": "John"}, {"name": "Alice"}]')
print("\n解析JSON字符串:")
for user in users_json:
print(f" {user.name} (年龄: {user.age})")4.3 与 JSON Schema 集成 #
Pydantic 模型可以生成 JSON Schema,这对于 API 文档生成、数据验证和前端开发非常有用。model_json_schema() 方法返回模型的 JSON Schema 定义。
# 导入必要的模块
from pydantic import BaseModel
# 定义用户模型
class User(BaseModel):
# 用户名,必填字段
name: str
# 年龄,可选字段
age: int = 18
# 测试代码
if __name__ == "__main__":
# 生成JSON Schema
schema = User.model_json_schema()
print("JSON Schema:")
print(schema)
"""
输出示例:
{
"title": "User",
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"age": {"title": "Age", "type": "integer", "default": 18},
},
"required": ["name"]
}
"""5. 性能优化 #
5.1 使用严格模式 #
Pydantic 提供了严格模式,使用 StrictInt、StrictStr 等类型可以防止自动类型转换,确保数据类型的严格性。这在需要精确控制数据类型的场景中非常有用。
# 导入必要的模块
from pydantic import StrictInt, StrictStr, BaseModel
# 定义严格模式模型
class StrictModel(BaseModel):
# 严格字符串类型,必须是字符串,不接受数字等
name: StrictStr
# 严格整数类型,必须是整数,不接受字符串数字
age: StrictInt
# 测试代码
if __name__ == "__main__":
try:
# 创建严格模式模型实例
model = StrictModel(name="Alice", age=25)
print("严格模式模型创建成功!")
print(f"名称: {model.name}, 年龄: {model.age}")
except Exception as e:
print(f"创建失败: {e}")
try:
# 尝试使用字符串数字作为年龄(应该失败)
model = StrictModel(name="Bob", age="25")
except Exception as e:
print(f"严格模式验证失败(预期): {e}")5.2 自定义类型 #
Pydantic 允许创建自定义类型,通过实现 __get_pydantic_core_schema__ 方法可以定义自己的验证逻辑。这对于特殊的数据格式或业务规则非常有用。
# 导入必要的模块
from pydantic import BaseModel, GetCoreSchemaHandler
from pydantic_core import core_schema
from typing import Any
# 定义自定义类型
class CustomType:
# 实现Pydantic核心schema方法
@classmethod
def __get_pydantic_core_schema__(
cls, source_type: Any, handler: GetCoreSchemaHandler
) -> core_schema.CoreSchema:
# 返回字符串schema,并在验证后调用validate方法
return core_schema.no_info_after_validator_function(
cls.validate,
core_schema.str_schema(),
)
# 自定义验证方法
@classmethod
def validate(cls, v: str) -> str:
# 检查字符串是否以"custom_"开头
if not v.startswith("custom_"):
raise ValueError("必须以custom_开头")
return v
# 定义使用自定义类型的模型
class MyModel(BaseModel):
# 使用自定义类型
value: CustomType
# 测试代码
if __name__ == "__main__":
try:
# 创建使用自定义类型的模型实例
model = MyModel(value="custom_test")
print("自定义类型验证成功!")
print(f"值: {model.value}")
except Exception as e:
print(f"创建失败: {e}")
try:
# 尝试使用无效值(不以custom_开头)
model = MyModel(value="invalid_test")
except Exception as e:
print(f"自定义类型验证失败(预期): {e}")6. 与 FastAPI 集成 #
Pydantic 是 FastAPI 的核心组件,用于请求和响应的数据验证。FastAPI 自动使用 Pydantic 模型来验证请求数据、生成响应模型和创建 API 文档。
# 导入必要的模块
from fastapi import FastAPI
from pydantic import BaseModel
# 创建FastAPI应用实例
app = FastAPI()
# 定义项目模型
class Item(BaseModel):
# 项目名称
name: str
# 项目描述,可选
description: str | None = None
# 项目价格
price: float
# 税费,可选
tax: float | None = None
# 定义POST端点,使用Pydantic模型验证请求数据
@app.post("/items/")
async def create_item(item: Item):
# 返回项目数据(自动转换为字典)
return {"item": item.model_dump()}
# 测试代码(模拟FastAPI请求)
if __name__ == "__main__":
# 模拟请求数据
request_data = {
"name": "Laptop",
"description": "High-performance laptop",
"price": 999.99,
"tax": 99.99
}
# 创建项目实例(模拟FastAPI的自动验证)
item = Item(**request_data)
print("FastAPI集成测试:")
print(f"项目: {item.name}")
print(f"价格: ${item.price}")
print(f"税费: ${item.tax}")7. 最佳实践 #
使用 Pydantic 时有一些重要的最佳实践,遵循这些实践可以提高代码质量、性能和可维护性。
- 明确类型:尽可能使用具体的类型注解
- 合理使用可选字段:使用
Optional或设置默认值 - 验证器简洁:保持验证器逻辑简单
- 文档注释:为模型和字段添加文档字符串
- 性能敏感场景:考虑使用严格模式或自定义类型
- 错误处理:妥善处理
ValidationError
8. 常见问题 #
8.1 循环引用 #
当模型之间存在循环引用时,需要使用 ForwardRef 来解决。这在复杂的数据结构中很常见,如用户和团队之间的相互引用。
# 导入必要的模块
from typing import ForwardRef
from pydantic import BaseModel
# 前向引用声明
UserRef = ForwardRef("User")
# 定义团队模型
class Team(BaseModel):
# 团队名称
name: str
# 团队成员列表(使用前向引用)
members: list[UserRef]
# 定义用户模型
class User(BaseModel):
# 用户名称
name: str
# 所属团队(可选)
team: Team | None = None
# 更新前向引用
Team.model_rebuild()
# 测试代码
if __name__ == "__main__":
try:
# 创建团队
team = Team(name="开发团队", members=[])
# 创建用户
user = User(name="张三", team=team)
print("循环引用模型创建成功!")
print(f"用户: {user.name}")
print(f"团队: {user.team.name if user.team else '无'}")
except Exception as e:
print(f"创建失败: {e}")8.2 自定义 JSON 编码 #
Pydantic 允许自定义 JSON 编码,这对于特殊的数据类型如 datetime 非常有用。可以通过配置 json_encoders 来自定义序列化行为。
# 导入必要的模块
from datetime import datetime
from pydantic import BaseModel
# 定义自定义JSON编码器模型
class CustomEncoder(BaseModel):
# 日期时间字段
dt: datetime
# 模型配置
class Config:
# 自定义JSON编码器
json_encoders = {
# 将datetime对象编码为字符串格式
datetime: lambda v: v.strftime("%Y-%m-%d")
}
# 测试代码
if __name__ == "__main__":
# 创建自定义编码器模型实例
model = CustomEncoder(dt=datetime(2023, 1, 1, 12, 0, 0))
# 转换为JSON
json_str = model.model_dump_json()
print("自定义JSON编码:")
print(json_str)
# 输出: {"dt": "2023-01-01"}9. 版本变化 #
Pydantic v2 相比 v1 有重要变化,包括性能提升、API 改进和架构优化。了解这些变化有助于正确使用新版本的功能。
Pydantic v2 的重要变化:
- 核心逻辑用 Rust 重写,性能大幅提升
- 新的
model_dump()和model_dump_json()方法替代旧的dict()和json() - 配置方式从类属性改为
model_config字典 - 更灵活的验证和序列化架构
总结 #
Pydantic 通过 Python 类型注解提供了强大的数据验证和转换功能,是现代 Python 开发中不可或缺的工具。它的主要优势在于:
- 简洁性:使用 Python 原生类型提示
- 强大验证:内置丰富的验证逻辑
- 高性能:核心部分用 Rust 实现
- 广泛集成:与 FastAPI、Django 等框架良好集成
掌握 Pydantic 可以显著提高数据处理代码的健壮性和可维护性,特别是在 API 开发和配置管理场景中。