本文目录导读:

- 使用
@field_validator装饰器(Pydantic v2) - 多字段联合验证
- 自定义数据类型和验证
- 使用
@validator装饰器(Pydantic v1 兼容) - 复杂的自定义验证器
- 使用
__init__和__post_init__进行验证 - 最佳实践建议
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
最佳实践建议
-
选择合适的验证器:
- 单个字段验证使用
@field_validator - 多字段依赖使用
@model_validator - 复杂业务逻辑可以创建自定义类型
- 单个字段验证使用
-
错误处理:
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()) # 获取详细的错误信息 -
性能考虑:
- 验证器的顺序是重要的,先验证简单的规则
- 避免在验证器中执行耗时操作
Pydantic 的验证系统非常灵活,可以根据具体需求选择合适的方式,记住在 v2 中推荐使用 @field_validator 和 @model_validator,而不是旧版的 @validator。