字数 3709,阅读大约需 19 分钟
Python:data validation library of pydantic
Pydantic 是一个 Python 数据验证和设置管理库,使用 Python 类型注解来定义数据模式。在大量开源Python代码里,都可以看到pydantic代码。在AI领域,OpenAI等公司,在其开源sdk里,也依赖pydantic进行数据验证和解析转换。
官方代码仓库: pydantic/pydantic: Data validation using Python type hints[1]
在FastAPI等框架里,也默认自动嵌入和pydantic,因此,掌握pydantic的使用语法,相当重要!
from typing import Any
from fastapi import FastAPI
from pydantic import BaseModel
class Plant(BaseModel):
species: str
height: float
flowering: bool
class Garden(BaseModel):
name: str
location: str
plants: list[Plant]
app = FastAPI()
@app.post("/gardens/", response_model=Garden)
async def create_garden(garden: Garden) -> Any:
return garden
主要作用
1. 数据验证
• 自动验证输入数据是否符合定义的类型
• 在运行时检查数据的正确性
• 提供详细的错误信息
2. 数据解析和转换
• 自动将输入数据转换为指定类型
• 处理 JSON、字典等格式的数据
• 支持复杂的嵌套数据结构
3. 设置管理
• 从环境变量、配置文件中加载配置
• 统一管理应用程序设置
核心价值
提高代码质量:通过类型检查减少运行时错误,让错误在数据进入系统时就被捕获。
提升开发效率:利用 Python 的类型注解,代码更易读、易维护,IDE 可以提供更好的自动补全和类型提示。
简化 API 开发:在 FastAPI、Django Ninja 等现代 Web 框架中广泛使用,自动生成 API 文档和请求验证。
性能优异:Pydantic v2 使用 Rust 编写核心,性能非常快。
结合type hints
from typing import Annotated, Literal
from annotated_types import Gt
from pydantic import BaseModel
class Fruit(BaseModel):
name: str
color: Literal['red', 'green']
weight: Annotated[float, Gt(0)]
bazam: dict[str, list[tuple[int, bool, float]]]
print(
Fruit(
name='Apple',
color='red',
weight=4.2,
bazam={'foobar': [(1, True, 0.1)]},
)
)
#> name='Apple' color='red' weight=4.2 bazam={'foobar': [(1, True, 0.1)]}
高性能校验数据
原文:Pydantic's core validation logic is implemented in a separate package (pydantic-core
[2]), where validation for most types is implemented in Rust.
pydantic V2是基于rust重新的版本,提供更快的数据验证和解析能力,是目前Python领域最快的!
1. 使用文档:The usage documentation[3] is the most complete guide on how to use Pydantic.
2. 安装文档:
uv add pydantic
Pydantic V2和V1
为什么要升级到V2,怎么兼容V1和V2
1、pydantic 2.0 对比1.0 ,性能有很大的提升
2、目前比较流行库的lib都依赖 pydantic 2.0
3、pydantic 2.0向下兼容1.0
兼容V1和V2
迁移指南 - Pydantic 官方文档[4],需要使用 pydantic.v1作为V1版本,而pydantic作为V2版本:
from pydantic.v1 import BaseModel
平滑升级
Pydantic V2 迁移指南:从 V1 升级到 V2 的关键变化-CSDN博客[5]
使用bump-pydantic,通过官方脚本工具升级V1->V2:pydantic/bump-pydantic: Convert Pydantic from V1 to V2 ♻[6]
Pydantic代码示例
BaseModel - Pydantic Validation[7]
from pydantic import BaseModel, Field, EmailStr, ValidationError, validator
from typing import List, Optional, Dict, Any
from datetime import datetime
import json
# --- 1. 最简单的Pydantic模型 ---
# 定义一个基本的用户模型,包含姓名和年龄
class User(BaseModel):
"""
基本用户模型,演示Pydantic最基础的数据验证和解析。
Pydantic模型通过类型注解定义数据结构,并在数据传入时自动进行验证和转换。
"""
# name 字段:字符串类型,使用 Field(...) 表示该字段是必填的。
# description 参数提供了字段的描述信息,有助于生成文档。
name: str = Field(..., description="用户的姓名,必填字段")
# age 字段:整数类型,使用 gt=0 约束年龄必须大于0。
# Field(...) 同样表示该字段是必填的。
age: int = Field(..., gt=0, description="用户的年龄,必须大于0,必填字段")
# --- 2. 基本数据验证和解析 ---
def demonstrate_basic_validation():
"""
演示Pydantic的基本数据验证和解析功能。
包括有效数据、缺少字段、字段类型错误和字段值不符合约束的场景。
"""
print("--- 2. 基本数据验证和解析 ---")
# 有效数据示例:创建一个符合 User 模型定义的用户实例
try:
user_data = {"name": "Alice", "age": 30}
# 通过字典解包(**user_data)将数据传递给模型,Pydantic会自动进行验证和实例化
user = User(**user_data)
print(f"有效用户数据解析成功: {user.json(indent=2)}")
# 断言验证,确保解析后的数据与预期一致
assert user.name == "Alice"
assert user.age == 30
except ValidationError as e:
# 捕获 ValidationError,打印错误信息
print(f"有效用户数据解析失败: {e}")
# 无效数据示例 - 缺少必填字段 'age'
try:
user_data_missing = {"name": "Bob"}
User(**user_data_missing)
except ValidationError as e:
print(f"无效用户数据 (缺少age字段) 解析失败: {e}")
# 无效数据示例 - 字段 'age' 类型错误 (期望 int,实际 str)
try:
user_data_invalid_type = {"name": "Charlie", "age": "twenty"}
User(**user_data_invalid_type)
except ValidationError as e:
print(f"无效用户数据 (age类型错误) 解析失败: {e}")
# 无效数据示例 - 字段 'age' 值不符合约束 (gt=0,实际 -5)
try:
user_data_invalid_value = {"name": "David", "age": -5}
User(**user_data_invalid_value)
except ValidationError as e:
print(f"无效用户数据 (age值不符合约束) 解析失败: {e}")
print("-" * 30)
# --- 3. 更多复杂的数据类型和字段验证 ---
class Product(BaseModel):
"""
产品模型,演示Pydantic中更复杂的数据类型和字段验证规则。
"""
# product_id 字段:字符串类型,使用 pattern 参数进行正则表达式验证。
# 要求格式为3个大写字母后跟4位数字。
product_id: str = Field(..., pattern=r"^[A-Z]{3}\d{4}$", description="产品ID,格式为3个大写字母+4位数字")
# name 字段:字符串类型,使用 min_length 和 max_length 约束字符串长度。
name: str = Field(..., min_length=2, max_length=50, description="产品名称,长度在2到50之间")
# price 字段:浮点数类型,使用 gt (大于) 和 le (小于等于) 约束数值范围。
price: float = Field(..., gt=0, le=10000, description="产品价格,必须大于0且小于等于10000")
# tags 字段:字符串列表类型,使用 default_factory=list 为可变类型提供默认值。
# 避免多个实例共享同一个可变默认值的问题。
tags: List[str] = Field(default_factory=list, description="产品标签列表,默认为空列表")
# is_available 字段:布尔类型,默认值为 True。
is_available: bool = Field(True, description="产品是否可用,默认为True")
# created_at 字段:datetime 类型,使用 default_factory=datetime.now 设置默认值为当前时间。
created_at: datetime = Field(default_factory=datetime.now, description="产品创建时间,默认为当前时间")
# description 字段:Optional[str] 表示该字段可以是字符串或 None,默认值为 None。
# 使用 max_length 约束字符串最大长度。
description: Optional[str] = Field(None, max_length=200, description="产品描述,可选,最大长度200")
def demonstrate_complex_types_and_validation():
"""
演示Pydantic中复杂数据类型和字段验证的使用。
包括正则表达式、长度限制、数值范围、列表和日期时间类型。
"""
print("--- 3. 复杂数据类型和字段验证 ---")
# 有效产品数据示例
try:
product_data = {
"product_id": "ABC1234",
"name": "Laptop Pro",
"price": 1200.50,
"tags": ["electronics", "computer"],
"is_available": True
}
product = Product(**product_data)
print(f"有效产品数据解析成功: {product.json(indent=2)}")
except ValidationError as e:
print(f"有效产品数据解析失败: {e}")
# 无效产品数据示例 - product_id 格式错误 (不符合正则表达式)
try:
invalid_product_id = {**product_data, "product_id": "abc1234"}
Product(**invalid_product_id)
except ValidationError as e:
print(f"无效产品数据 (product_id格式错误) 解析失败: {e}")
# 无效产品数据示例 - name 长度错误 (小于 min_length)
try:
invalid_name = {**product_data, "name": "L"}
Product(**invalid_name)
except ValidationError as e:
print(f"无效产品数据 (name长度错误) 解析失败: {e}")
# 无效产品数据示例 - price 超出范围 (大于 le 约束)
try:
invalid_price = {**product_data, "price": 15000.00}
Product(**invalid_price)
except ValidationError as e:
print(f"无效产品数据 (price超出范围) 解析失败: {e}")
print("-" * 30)
# --- 4. 嵌套模型 ---
class Address(BaseModel):
"""
地址模型,作为其他模型中的嵌套字段使用。
"""
street: str = Field(..., description="街道名称")
city: str = Field(..., description="城市名称")
# zip_code 字段:字符串类型,使用 pattern 约束为6位数字。
zip_code: str = Field(..., pattern=r"^\d{6}$", description="邮政编码,6位数字")
class Customer(BaseModel):
"""
客户模型,演示如何在一个Pydantic模型中嵌套另一个Pydantic模型。
"""
customer_id: int = Field(..., gt=0, description="客户ID,必须大于0")
name: str = Field(..., description="客户姓名")
# EmailStr 类型:Pydantic 内置的邮箱格式验证器,自动检查字符串是否为有效邮箱。
email: EmailStr = Field(..., description="客户邮箱,Pydantic会自动验证邮箱格式")
# shipping_address 字段:类型为 Address 模型,表示这是一个必填的嵌套地址对象。
shipping_address: Address = Field(..., description="收货地址,必填,是一个嵌套的Address模型")
# billing_address 字段:Optional[Address] 表示账单地址是可选的,可以为 None。
billing_address: Optional[Address] = Field(None, description="账单地址,可选,是一个嵌套的Address模型")
# orders 字段:列表,其中每个元素是字典,字典的键是字符串,值可以是任意类型。
# default_factory=list 提供默认空列表。
orders: List[Dict[str, Any]] = Field(default_factory=list, description="订单列表,默认为空列表")
def demonstrate_nested_models():
"""
演示Pydantic如何处理嵌套模型,以及如何访问嵌套模型中的字段。
"""
print("--- 4. 嵌套模型 ---")
# 有效客户数据示例,包含嵌套的 shipping_address
try:
customer_data = {
"customer_id": 101,
"name": "Eve",
"email": "[email protected]",
"shipping_address": {
"street": "123 Main St",
"city": "Anytown",
"zip_code": "100001"
},
"orders": [
{"order_id": "ORD001", "amount": 50.0},
{"order_id": "ORD002", "amount": 75.5}
]
}
customer = Customer(**customer_data)
print(f"有效客户数据解析成功: {customer.json(indent=2)}")
# 访问嵌套字段的属性
print(f"客户收货城市: {customer.shipping_address.city}")
except ValidationError as e:
print(f"有效客户数据解析失败: {e}")
# 无效客户数据示例 - 嵌套地址的邮政编码格式错误
try:
invalid_address_customer = {
**customer_data,
"shipping_address": {
"street": "456 Oak Ave",
"city": "Otherville",
"zip_code": "123" # 错误格式,不符合6位数字的pattern
}
}
Customer(**invalid_address_customer)
except ValidationError as e:
print(f"无效客户数据 (嵌套地址邮政编码错误) 解析失败: {e}")
print("-" * 30)
# --- 5. 配置选项 (Config) ---
class Settings(BaseModel):
"""
配置模型,演示Pydantic v1的Config类,用于配置模型的行为。
在Pydantic v2中,推荐使用 `model_config` 属性。
"""
app_name: str = Field(..., description="应用程序名称")
admin_email: EmailStr = Field(..., description="管理员邮箱")
debug_mode: bool = Field(False, description="是否开启调试模式")
# Config 类用于定义模型的配置选项
class Config:
extra = "ignore" # 当传入数据包含模型中未定义的字段时,忽略这些额外字段而不是抛出错误。
allow_mutation = True # 允许模型实例在创建后修改字段值 (Pydantic v1 默认就是 True)。
# 在 Pydantic v2 中,默认是不可变的,需要通过 model_config['frozen'] = False 来实现。
anystr_strip_whitespace = True # 自动去除所有字符串字段前后的空白字符。
def demonstrate_model_config():
"""
主要展示 extra="ignore" 和 anystr_strip_whitespace 的效果。
"""
print("--- 5. 配置选项 (model_config) ---")
# 包含额外字段的数据,由于 extra="ignore",这些额外字段会被忽略。
# app_name 字段有前后空白,由于 anystr_strip_whitespace=True,空白会被自动去除。
try:
settings_data = {
"app_name": " My App ", # 字符串有前后空白
"admin_email": "[email protected]",
"debug_mode": True,
"version": "1.0.0", # 额外字段,将被忽略
"env": "dev" # 额外字段,将被忽略
}
settings = Settings(**settings_data)
print(f"配置模型解析成功 (额外字段被忽略,字符串空白被去除): {settings.json(indent=2)}")
assert settings.app_name == "My App" # 验证空白已被去除
# 尝试修改 allow_mutation=True 的模型字段 (在 Pydantic v1 中默认允许)
settings.debug_mode = False
print(f"修改 debug_mode 后: {settings.json(indent=2)}")
except ValidationError as e:
print(f"配置模型解析失败: {e}")
# 尝试使用 extra="forbid" (Pydantic的默认行为,不允许额外字段)
class StrictSettings(BaseModel):
app_name: str
class Config:
extra = "forbid" # 显式声明 extra="forbid",这是 Pydantic 的默认行为。
try:
# 传入一个模型中未定义的字段 'unknown_field',这将导致 ValidationError
StrictSettings(app_name="Test", unknown_field="value")
except ValidationError as e:
print(f"严格模式下 (extra='forbid') 额外字段解析失败: {e}")
print("-" * 30)
# --- 6. 自定义验证器 (使用 @validator) ---
# 自定义验证函数:将字符串转换为大写
def to_uppercase(v: str) -> str:
"""
将输入的字符串转换为大写。
如果输入不是字符串,则抛出 ValueError。
"""
if isinstance(v, str):
return v.upper()
raise ValueError("输入必须是字符串")
# 自定义验证函数:检查列表长度
def check_list_length(v: List[Any]) -> List[Any]:
"""
检查列表长度是否在1到3之间。
如果不在该范围内,则抛出 ValueError。
"""
if 1 <= len(v) <= 3:
return v
raise ValueError("列表长度必须在1到3之间")
class Item(BaseModel):
"""
商品模型,演示如何使用 @validator 装饰器定义自定义验证器。
"""
name: str = Field(..., description="商品名称,会自动转换为大写")
item_id: int = Field(..., gt=0, description="商品ID,必须大于0")
tags: List[str] = Field(..., description="商品标签,列表长度必须在1到3之间")
# @validator('name', pre=True) 装饰器:
# 'name' 指定这个验证器应用于 name 字段。
# pre=True 表示这个验证器在字段类型转换之前运行。
# 这里将 name 字段的值在类型转换前转换为大写。
@validator('name', pre=True)
def validate_name_to_uppercase(cls, v):
"""
自定义验证器,在处理 name 字段之前将其转换为大写。
"""
return to_uppercase(v)
# @validator('tags') 装饰器:
# 'tags' 指定这个验证器应用于 tags 字段。
# 默认情况下,验证器在字段类型转换之后运行。
# 这里检查 tags 列表的长度。
@validator('tags')
def validate_tags_length(cls, v):
"""
自定义验证器,检查 tags 列表的长度是否符合要求。
"""
return check_list_length(v)
def demonstrate_custom_validators():
print("--- 6. 自定义验证器 ---")
# 有效数据示例:name 会被转换为大写,tags 长度符合要求
try:
item_data = {
"name": "apple",
"item_id": 1,
"tags": ["fruit", "red"]
}
item = Item(**item_data)
print(f"有效商品数据解析成功: {item.json(indent=2)}")
assert item.name == "APPLE" # 验证器已生效,name 变为大写
except ValidationError as e:
print(f"有效商品数据解析失败: {e}")
# 无效数据示例 - name 不是字符串 (to_uppercase 验证器会抛出 ValueError)
try:
invalid_name_item = {**item_data, "name": 123}
Item(**invalid_name_item)
except ValidationError as e:
print(f"无效商品数据 (name不是字符串) 解析失败: {e}")
# 无效数据示例 - tags 列表长度不符合要求 (check_list_length 验证器会抛出 ValueError)
try:
invalid_tags_item = {**item_data, "tags": ["fruit", "red", "sweet", "big"]}
Item(**invalid_tags_item)
except ValidationError as e:
print(f"无效商品数据 (tags列表长度错误) 解析失败: {e}")
print("-" * 30)
# --- 7. 从JSON字符串解析和导出 ---
def demonstrate_json_handling():
"""
演示Pydantic模型如何从JSON字符串解析数据,以及如何将模型实例导出为JSON字符串。
"""
print("--- 7. 从JSON字符串解析和导出 ---")
user_json_str = '{"name": "Frank", "age": 40}'
try:
# User.parse_raw(json_string) 方法:
# 从 JSON 字符串直接解析数据并创建模型实例。
user = User.parse_raw(user_json_str)
print(f"从JSON字符串解析成功: {user.json(indent=2)}")
# user.json(indent=2) 方法:
# 将模型实例转换为 JSON 格式的字符串。
# indent=2 参数使输出的 JSON 字符串格式化,更易读。
exported_json_str = user.json(indent=2)
print(f"导出为JSON字符串:\n{exported_json_str}")
# 验证导出的 JSON 字符串与原始数据是否一致
assert json.loads(exported_json_str) == {"name": "Frank", "age": 40}
except ValidationError as e:
print(f"JSON处理失败: {e}")
print("-" * 30)
# --- 8. 运行时执行所有演示函数 ---
if __name__ == "__main__":
# 当脚本直接运行时,依次调用所有演示函数,展示Pydantic的各项功能。
demonstrate_basic_validation()
demonstrate_complex_types_and_validation()
demonstrate_nested_models()
demonstrate_model_config()
demonstrate_custom_validators()
demonstrate_json_handling()
引用链接
[1]
pydantic/pydantic: Data validation using Python type hints: https://github.com/pydantic/pydantic[2]
pydantic-core
: https://github.com/pydantic/pydantic-core[3]
usage documentation: https://docs.pydantic.dev/latest/concepts/models/[4]
迁移指南 - Pydantic 官方文档: https://pydantic.com.cn/migration/#_7[5]
Pydantic V2 迁移指南:从 V1 升级到 V2 的关键变化-CSDN博客: https://blog.csdn.net/gitblog_01134/article/details/148361702[6]
pydantic/bump-pydantic: Convert Pydantic from V1 to V2 ♻: https://github.com/pydantic/bump-pydantic[7]
BaseModel - Pydantic Validation: https://docs.pydantic.dev/latest/api/base_model/
评论区