1. Pydantic类型系统概览
Pydantic的类型系统建立在Python类型提示(Type Hints)之上,但赋予了类型更强大的能力——运行时数据验证。与静态类型检查工具不同,Pydantic会在运行时对输入数据进行类型检查和转换,确保数据符合类型定义。
传统Python类型提示只是给IDE和静态检查工具使用的"注释",而Pydantic通过pydantic-core引擎将这些类型提示转化为实际的验证逻辑。当数据不符合类型定义时,Pydantic会抛出详细的验证错误,而不是像普通Python代码那样在运行时才可能报错。
Pydantic支持的类型主要分为三类:
- 标准库类型:包括
int,str,bool,float,list,dict,tuple等Python内置类型 - Pydantic扩展类型:如
EmailStr,UrlStr,PositiveInt等增强类型 - 自定义类型:开发者可以创建自己的验证类型
2. 基础类型注解与验证
2.1 标准类型的使用
Pydantic对标准Python类型的处理既直观又强大。下面是一个基本示例:
python复制from pydantic import BaseModel
class User(BaseModel):
name: str
age: int
is_active: bool
# 自动进行类型转换和验证
user = User(name="John", age="30", is_active="yes")
print(user)
# 输出: name='John' age=30 is_active=True
在这个例子中,Pydantic自动将字符串"30"转换为整数30,将"yes"转换为布尔值True。这种宽松转换是Pydantic的默认行为,可以通过配置调整为严格模式。
2.2 嵌套类型与复杂结构
Pydantic可以优雅地处理嵌套类型和复杂数据结构:
python复制from typing import List, Dict, Optional
from pydantic import BaseModel
class Address(BaseModel):
street: str
city: str
zip_code: str
class User(BaseModel):
name: str
age: int
addresses: List[Address] # 地址列表
metadata: Dict[str, str] # 字符串键值对
nickname: Optional[str] = None # 可选字段
user_data = {
"name": "Alice",
"age": 25,
"addresses": [
{"street": "123 Main St", "city": "New York", "zip_code": "10001"},
{"street": "456 Oak Ave", "city": "Boston", "zip_code": "02108"}
],
"metadata": {"department": "Engineering", "role": "Developer"}
}
user = User(**user_data)
print(user.addresses[0].city) # 输出: New York
3. 高级类型特性
3.1 严格类型(Strict Types)
Pydantic提供了一系列严格类型,它们不会尝试类型转换,只接受确切的类型匹配:
python复制from pydantic import BaseModel, StrictInt, StrictStr, StrictBool
class StrictData(BaseModel):
num: StrictInt
text: StrictStr
flag: StrictBool
# 这会成功
data = StrictData(num=123, text="abc", flag=True)
# 这会失败,因为字符串"123"不是整数
try:
StrictData(num="123", text="abc", flag=True)
except Exception as e:
print(e)
# 输出: 1 validation error for StrictData
# num
# Input should be a valid integer [type=int_type, input_value='123', input_type=str]
Pydantic提供的严格类型包括:
StrictIntStrictFloatStrictStrStrictBoolStrictBytes
3.2 约束类型(Constrained Types)
Pydantic允许你为基本类型添加各种约束条件:
python复制from pydantic import BaseModel, conint, constr
class Product(BaseModel):
id: conint(gt=0) # 必须大于0的整数
name: constr(min_length=1, max_length=50) # 长度限制的字符串
price: confloat(ge=0) # 必须大于等于0的浮点数
# 有效数据
valid = Product(id=1, name="Laptop", price=999.99)
# 无效数据
try:
Product(id=0, name="", price=-10)
except Exception as e:
print(e)
# 会输出多个验证错误
常用的约束类型函数包括:
conint()- 约束整数confloat()- 约束浮点数constr()- 约束字符串conlist()- 约束列表conset()- 约束集合condate()- 约束日期
4. 自定义类型开发
4.1 使用Annotated创建自定义类型
Python 3.9+引入了Annotated类型,Pydantic利用它来创建自定义验证类型:
python复制from typing import Annotated
from pydantic import BaseModel, Field, ValidationError
# 定义正整数的类型别名
PositiveInt = Annotated[int, Field(gt=0)]
class Inventory(BaseModel):
stock: PositiveInt
# 有效
inv = Inventory(stock=10)
# 无效
try:
Inventory(stock=-5)
except ValidationError as e:
print(e)
# 输出: 1 validation error for Inventory
# stock
# Input should be greater than 0 [type=greater_than, input_value=-5, input_type=int]
4.2 完全自定义类型
对于更复杂的需求,可以实现__get_pydantic_core_schema__方法创建完全自定义的类型:
python复制from pydantic_core import core_schema
from pydantic import BaseModel, GetCoreSchemaHandler
class CapitalizedStr(str):
@classmethod
def __get_pydantic_core_schema__(
cls, source_type, handler: GetCoreSchemaHandler
) -> core_schema.CoreSchema:
def validate(value: str) -> str:
if not value[0].isupper():
raise ValueError("String must be capitalized")
return cls(value)
return core_schema.no_info_after_validator_function(
validate,
handler(str),
)
class Person(BaseModel):
name: CapitalizedStr
# 有效
p = Person(name="John")
# 无效
try:
Person(name="john")
except Exception as e:
print(e)
# 输出: 1 validation error for Person
# name
# String must be capitalized [type=value_error, input_value='john', input_type=str]
5. 类型转换与验证细节
5.1 宽松模式 vs 严格模式
Pydantic默认使用宽松模式进行类型转换:
python复制from pydantic import BaseModel
class Example(BaseModel):
number: int
# 宽松模式下,字符串会自动转换为整数
obj = Example(number="123")
print(obj.number) # 输出: 123 (整数)
可以通过模型配置启用严格模式:
python复制from pydantic import BaseModel, ConfigDict
class StrictExample(BaseModel):
model_config = ConfigDict(strict=True)
number: int
# 严格模式下会报错
try:
StrictExample(number="123")
except Exception as e:
print(e)
# 输出: 1 validation error for StrictExample
# number
# Input should be a valid integer [type=int_type, input_value='123', input_type=str]
5.2 自定义验证器
除了类型系统,Pydantic还提供了@validator装饰器来添加更复杂的验证逻辑:
python复制from pydantic import BaseModel, validator
class UserProfile(BaseModel):
username: str
password: str
@validator('password')
def password_complexity(cls, v):
if len(v) < 8:
raise ValueError("Password must be at least 8 characters")
if not any(c.isupper() for c in v):
raise ValueError("Password must contain at least one uppercase letter")
if not any(c.isdigit() for c in v):
raise ValueError("Password must contain at least one digit")
return v
# 有效密码
valid = UserProfile(username="johndoe", password="Passw0rd")
# 无效密码
try:
UserProfile(username="johndoe", password="weak")
except Exception as e:
print(e)
# 会输出密码复杂度相关的错误
6. 实战应用技巧
6.1 处理第三方类型
当需要与第三方库的类型集成时,可以创建适配器:
python复制from pydantic import BaseModel, GetCoreSchemaHandler
from pydantic_core import core_schema
import some_third_party_lib
class ThirdPartyTypeAdapter:
@classmethod
def __get_pydantic_core_schema__(
cls, source_type, handler: GetCoreSchemaHandler
) -> core_schema.CoreSchema:
def validate(value):
try:
return some_third_party_lib.parse(value)
except some_third_party_lib.Error as e:
raise ValueError(f"Invalid third party value: {e}")
return core_schema.no_info_after_validator_function(
validate,
handler(str), # 假设第三方类型从字符串解析
)
ThirdPartyType = Annotated[str, ThirdPartyTypeAdapter]
class MyModel(BaseModel):
data: ThirdPartyType
6.2 性能优化建议
-
尽量使用Pydantic内置类型:内置类型的验证逻辑是用Rust实现的(pydantic-core),性能远高于Python实现的验证器。
-
避免过度复杂的嵌套:深层嵌套的结构会增加验证时间,必要时可以考虑扁平化数据结构。
-
合理使用严格模式:严格模式跳过类型转换,通常比宽松模式更快。
-
缓存模型定义:重复使用相同的模型实例比每次都创建新实例更高效。
-
考虑使用TypeAdapter:对于简单的一次性验证,
TypeAdapter比完整的BaseModel更轻量:
python复制from pydantic import TypeAdapter
validate_int = TypeAdapter(int).validate_python
print(validate_int("123")) # 输出: 123
7. 常见问题与解决方案
7.1 循环引用问题
当类型相互引用时,可以使用ForwardRef或字符串字面量:
python复制from typing import ForwardRef
from pydantic import BaseModel
class Department(BaseModel):
name: str
employees: list["Employee"] # 使用字符串字面量
class Employee(BaseModel):
name: str
department: ForwardRef("Department") # 使用ForwardRef
# 解析前向引用
Employee.model_rebuild()
# 现在可以创建循环结构
sales = Department(name="Sales", employees=[])
john = Employee(name="John", department=sales)
sales.employees.append(john)
7.2 处理泛型类型
Pydantic完全支持Python的泛型类型系统:
python复制from typing import Generic, TypeVar
from pydantic import BaseModel
T = TypeVar('T')
class PaginatedResponse(BaseModel, Generic[T]):
data: list[T]
page: int
total_pages: int
# 使用具体类型实例化
UserResponse = PaginatedResponse["User"]
PostResponse = PaginatedResponse["Post"]
7.3 自定义错误消息
可以通过Field自定义验证错误消息:
python复制from pydantic import BaseModel, Field, ValidationError
class Product(BaseModel):
price: float = Field(..., gt=0, description="Price must be positive")
try:
Product(price=-10)
except ValidationError as e:
print(e.errors()[0]["ctx"])
# 输出: {'gt': 0.0}
更复杂的错误消息定制可以通过覆盖__get_pydantic_core_schema__实现。
