Bootstrap

【Pydantic】Python 数据验证入门

1. 什么是 Pydantic?

Pydantic 是一个功能强大的 Python 数据验证库,它通过 Python 类型注解实现快速的数据验证和转换。它不仅提供了全面的类型验证、自动数据转换和详细的错误处理机制,还具有基于 Rust 实现的高性能核心验证器。凭借其优秀的 IDE 支持和可扩展性,Pydantic 在 FastAPI 等 Web 框架、配置管理、数据序列化以及 API 接口模型定义等多个场景中得到广泛应用。

2. 安装

pip install pydantic
pip install pydantic-settings

3. 基础使用

在开始使用 Pydantic 之前,我们需要了解它的核心概念和基本用法。Pydantic 的核心是通过定义模型类来实现数据验证,这些模型类继承自 BaseModel。

3.1 创建基础模型

基础模型是 Pydantic 中最常用的功能。通过继承 BaseModel 类,我们可以定义数据结构和类型约束。每个字段都可以使用 Python 的类型注解来指定其类型,这些类型会在数据验证时被强制执行。

from pydantic import BaseModel
from typing import Optional
from datetime import datetime

class User(BaseModel):
    id: int
    username: str
    email: str
    full_name: Optional[str] = None  # 可选字段
    created_at: datetime = datetime.now()  # 默认值

# 创建实例
user = User(
    id=1,
    username="john_doe",
    email="[email protected]"
)

# 访问数据
print(user.username)  # 输出: john_doe

# 转换为字典
user_dict = user.model_dump()

字段定义说明:
• id: int - 必填的整数字段
• username: str - 必填的字符串字段
• email: str - 必填的邮箱字段
• full_name: Optional[str] - 可选的字符串字段,可以为 None
• created_at: datetime - 带默认值的日期时间字段

3.2 数据验证

Pydantic 的数据验证是自动进行的,当创建模型实例时,输入数据会自动根据定义的类型进行验证和转换。如果验证失败,Pydantic 会抛出详细的 ValidationError 异常。

from pydantic import BaseModel, EmailStr, ValidationError

class UserRegistration(BaseModel):
    username: str
    email: EmailStr
    age: int

try:
    # 尝试创建无效数据
    user = UserRegistration(
        username="john",
        email="invalid-email",  # 无效邮箱
        age="not_a_number"  # 无效年龄
    )
except ValidationError as e:
    print("验证错误:")
    for error in e.errors():
        print(f"- {error['loc'][0]}: {error['msg']}")

当验证失败时,错误信息会包含:

• 具体是哪个字段验证失败
• 失败的原因
• 期望的数据类型
• 实际接收到的数据类型

3.3 嵌套模型

在实际应用中,数据结构往往是嵌套的。Pydantic 完全支持模型嵌套,这使得我们可以构建复杂的数据结构,同时保持数据验证的严谨性。

from typing import List

class Address(BaseModel):
    street: str
    city: str
    country: str

class User(BaseModel):
    name: str
    addresses: List[Address]

# 使用嵌套模型
user = User(
    name="John Doe",
    addresses=[
        {"street": "123 Main St", "city": "Boston", "country": "USA"},
        {"street": "456 Park Ave", "city": "New York", "country": "USA"}
    ]
)

嵌套模型的优势:

• 保持数据结构的层次清晰
• 支持复杂的数据验证逻辑
• 方便数据的序列化和反序列化

4. 常用验证规则

Pydantic 提供了丰富的验证规则,可以满足各种复杂的数据验证需求。这些规则可以组合使用,构建出强大的验证逻辑。

4.1 基础验证规则

Field 类是 Pydantic 提供的字段定义工具,它允许我们为字段添加各种验证规则和元数据。通过 Field,我们可以定义字段的约束条件、默认值和描述信息。

from pydantic import BaseModel, Field, EmailStr, HttpUrl, constr

class User(BaseModel):
    # 字符串验证
    name: str = Field(..., min_length=2, max_length=50)  # 必填,长度2-50
    username: str = Field(..., pattern="^[a-zA-Z0-9_-]+$")  # 只允许字母、数字、下划线和横杠

    # 数值验证
    age: int = Field(..., ge=0, le=120)  # 大于等于0,小于等于120
    score: float = Field(..., gt=0, lt=100)  # 大于0,小于100

    # 特殊类型验证
    email: EmailStr  # 邮箱验证
    website: HttpUrl  # URL验证

    # 可选字段
    description: str | None = Field(None, max_length=1000)  # 可选,最大长度1000

验证规则说明:
 • min_length/max_length: 控制字符串长度
 • pattern: 使用正则表达式验证字符串格式
 • ge/le: 大于等于/小于等于
 • gt/lt: 大于/小于

4.2 列表验证

from typing import List

class Order(BaseModel):
    # 列表长度验证
    items: List[str] = Field(..., min_items=1, max_items=10)

    # 列表元素唯一性验证
    tags: List[str] = Field(..., unique_items=True)

    # 价格必须为正数的列表
    prices: List[float] = Field(..., gt=0)

4.3 自定义验证器

from pydantic import BaseModel, model_validator, field_validator
from datetime import datetime

class Order(BaseModel):
    order_id: str
    created_at: datetime
    total_amount: float
    items_count: int

    # 字段级验证器
    @field_validator('order_id')
    def validate_order_id(cls, v):
        if not v.startswith('ORD-'):
            return f'ORD-{v}'
        return v

    # 模型级验证器
    @model_validator(mode='after')
    def validate_total(self):
        if self.total_amount <= 0 and self.items_count > 0:
            raise ValueError('Total amount must be positive when items exist')
        return self

4.4 条件验证

from typing import Optional
from pydantic import BaseModel, Field, model_validator

class Product(BaseModel):
    name: str
    price: float
    discount: Optional[float] = None
    final_price: Optional[float] = None

    @model_validator(mode='after')
    def calculate_final_price(self):
        if self.discount is not None:
            if not 0 <= self.discount <= 1:
                raise ValueError('Discount must be between 0 and 1')
            self.final_price = self.price * (1 - self.discount)
        else:
            self.final_price = self.price
        return self

4.5 常用验证类型

from pydantic import BaseModel, Field, EmailStr, HttpUrl, conint, confloat, constr

class UserProfile(BaseModel):
    # 受约束的字符串
    username: constr(min_length=3, max_length=20, pattern="^[a-zA-Z0-9_]+$")

    # 受约束的整数
    age: conint(ge=0, le=120)

    # 受约束的浮点数
    height: confloat(ge=0, le=300)

    # 枚举选择
    status: str = Field(..., pattern="^(active|inactive|pending)$")

    # 布尔值
    is_active: bool = True

这些验证规则涵盖了日常开发中最常见的数据验证场景。通过组合使用这些规则,可以构建出复杂的数据验证逻辑。记住几个要点:

  1. Field(…) 中的 … 表示该字段必填
  2. 使用 Optional 或 | None 表示可选字段
  3. 验证器分为字段级 (field_validator) 和模型级 (model_validator)
  4. 可以组合多个验证规则
  5. 验证规则的顺序: 先定义字段,再添加验证规则
  6. 验证规则的执行顺序: 先定义的规则先执行

5. 实际应用示例

5.1 API 请求验证

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float
    is_offer: bool = False

@app.post("/items/")
async def create_item(item: Item):
    return item

5.2 配置管理

from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
    database_url: str
    api_key: str
    debug: bool = False

    model_config = SettingsConfigDict(env_file='.env')

# 从环境变量加载配置
settings = Settings()

6. 小技巧

6.1. 数据转换:

# 字典转模型
data = {"name": "John", "age": "25"}  # 注意 age 是字符串
user = User.model_validate(data)  # 推荐使用 model_validate 而不是 parse_obj

# 模型转 JSON
json_str = user.model_dump_json()  # 推荐使用 model_dump_json 而不是 json()

# 模型转字典
user_dict = user.model_dump()  # 推荐使用 model_dump 而不是 dict()

# 模型转 JSON 字符串(带缩进)
json_str = user.model_dump_json(indent=4)

# JSON 字符串转模型
user = User.model_validate_json(json_str)

6.2 错误处理:

try:
    user = User(name="John", age="invalid")
except ValidationError as e:
    print("数据无效:", e)

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;