Pydantic自定义验证怎么实现

wen python案例 1

本文目录导读:

Pydantic自定义验证怎么实现

  1. 使用 @field_validator 装饰器(Pydantic v2)
  2. 多字段联合验证
  3. 自定义数据类型和验证
  4. 使用 @validator 装饰器(Pydantic v1 兼容)
  5. 复杂的自定义验证器
  6. 使用 __init____post_init__ 进行验证
  7. 最佳实践建议

Pydantic 提供了多种实现自定义验证的方式,我来详细介绍几种常用方法:

使用 @field_validator 装饰器(Pydantic v2)

from pydantic import BaseModel, field_validator
class User(BaseModel):
    name: str
    age: int
    email: str
    @field_validator('age')
    @classmethod
    def validate_age(cls, v):
        if v < 0 or v > 150:
            raise ValueError('年龄必须在0-150之间')
        return v
    @field_validator('email')
    @classmethod
    def validate_email(cls, v):
        if '@' not in v:
            raise ValueError('邮箱格式不正确')
        return v
# 测试
try:
    user = User(name="张三", age=200, email="test@example.com")
except Exception as e:
    print(e)  # 会报错,因为年龄超出范围

多字段联合验证

from pydantic import BaseModel, model_validator
class RegistrationForm(BaseModel):
    username: str
    password: str
    confirm_password: str
    @model_validator(mode='after')
    def check_passwords_match(self):
        if self.password != self.confirm_password:
            raise ValueError('两次密码输入不一致')
        return self
    @model_validator(mode='after')
    def validate_username(self):
        if len(self.username) < 3:
            raise ValueError('用户名至少需要3个字符')
        return self
# 测试
try:
    form = RegistrationForm(
        username="ab",
        password="123456",
        confirm_password="123456"
    )
except Exception as e:
    print(e)

自定义数据类型和验证

from pydantic import BaseModel
from typing import Annotated
from pydantic.functional_validators import AfterValidator
import re
def validate_phone_number(v: str) -> str:
    """自定义手机号验证"""
    # 去除空格和横线
    phone = v.replace(' ', '').replace('-', '')
    # 验证手机号格式
    pattern = r'^1[3-9]\d{9}$'
    if not re.match(pattern, phone):
        raise ValueError('手机号格式不正确')
    return phone
# 使用 Annotated 进行类型注解
PhoneStr = Annotated[str, AfterValidator(validate_phone_number)]
class Contact(BaseModel):
    name: str
    phone: PhoneStr  # 使用自定义类型
# 测试
try:
    contact = Contact(name="李四", phone="138-0013-8000")
    print(contact)  # 手机号会被自动处理
except Exception as e:
    print(e)

使用 @validator 装饰器(Pydantic v1 兼容)

from pydantic import BaseModel, validator
class Product(BaseModel):
    name: str
    price: float
    quantity: int
    @validator('price')
    def validate_price(cls, v):
        if v <= 0:
            raise ValueError('价格必须大于0')
        return round(v, 2)
    @validator('quantity')
    def validate_quantity(cls, v):
        if v < 0:
            raise ValueError('数量不能为负数')
        return v
# 测试
try:
    product = Product(name="商品", price=19.999, quantity=10)
    print(product.price)  # 输出 20.0 (四舍五入)
except Exception as e:
    print(e)

复杂的自定义验证器

from pydantic import BaseModel, field_validator
from typing import List, Dict
import re
class Order(BaseModel):
    order_id: str
    items: List[Dict[str, float]]
    discount: float = 0.0
    @field_validator('order_id')
    @classmethod
    def validate_order_id(cls, v):
        # 自定义订单号格式:ORD-YYYYMMDD-XXXXX
        pattern = r'^ORD-\d{8}-[A-Z0-9]{5}$'
        if not re.match(pattern, v):
            raise ValueError(f'订单号格式不正确: {v}')
        return v
    @field_validator('items')
    @classmethod
    def validate_items(cls, v):
        if not v:
            raise ValueError('订单至少包含一个商品')
        for item in v:
            for name, price in item.items():
                if not isinstance(name, str) or not name.strip():
                    raise ValueError('商品名称不能为空')
                if price <= 0:
                    raise ValueError(f'商品 {name} 的价格必须大于0')
        return v
    @field_validator('discount')
    @classmethod
    def validate_discount(cls, v):
        if v < 0 or v > 1:
            raise ValueError('折扣必须在0到1之间')
        return v
    def calculate_total(self) -> float:
        total = sum(
            price for item in self.items 
            for name, price in item.items()
        )
        return total * (1 - self.discount)
# 测试
try:
    order = Order(
        order_id="ORD-20231231-ABC12",
        items=[{"商品A": 100.0}, {"商品B": 200.0}],
        discount=0.1
    )
    print(f"订单总价: {order.calculate_total()}")  # 输出: 270.0
except Exception as e:
    print(e)

使用 __init____post_init__ 进行验证

from pydantic import BaseModel
from typing import Optional
class Config(BaseModel):
    host: str
    port: int
    timeout: Optional[int] = 30
    def __post_init__(self):
        """在初始化后执行额外的验证"""
        if self.port < 1 or self.port > 65535:
            raise ValueError('端口号必须在1-65535之间')
        if not self.host.startswith(('http://', 'https://')):
            self.host = f"http://{self.host}"
# 测试
config = Config(host="localhost", port=8080)
print(config.host)  # 输出: http://localhost

最佳实践建议

  1. 选择合适的验证器

    • 单个字段验证使用 @field_validator
    • 多字段依赖使用 @model_validator
    • 复杂业务逻辑可以创建自定义类型
  2. 错误处理

    class UserModel(BaseModel):
        name: str
        @field_validator('name')
        @classmethod
        def validate_name(cls, v):
            if not v:
                raise ValueError('名称不能为空')
            return v.strip()
    # 捕获具体错误
    try:
        user = UserModel(name="")
    except ValidationError as e:
        print(e.errors())  # 获取详细的错误信息
  3. 性能考虑

    • 验证器的顺序是重要的,先验证简单的规则
    • 避免在验证器中执行耗时操作

Pydantic 的验证系统非常灵活,可以根据具体需求选择合适的方式,记住在 v2 中推荐使用 @field_validator@model_validator,而不是旧版的 @validator

抱歉,评论功能暂时关闭!