目 录CONTENT

文章目录

Python:data validation library of pydantic

Administrator
2025-10-10 / 0 评论 / 0 点赞 / 0 阅读 / 0 字

 

字数 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. 1. 数据验证

    • • 自动验证输入数据是否符合定义的类型

    • • 在运行时检查数据的正确性

    • • 提供详细的错误信息

  2. 2. 数据解析和转换

    • • 自动将输入数据转换为指定类型

    • • 处理 JSON、字典等格式的数据

    • • 支持复杂的嵌套数据结构

  3. 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. 1. 使用文档:The usage documentation[3] is the most complete guide on how to use Pydantic.

  2. 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/

 

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区