大家好,我是jobleap.cn的小九。
Pydantic 是 Python 生态中最主流的数据验证与序列化库,核心基于类型注解实现「声明式数据校验」,广泛应用于接口参数校验、配置解析、数据序列化/反序列化等场景(如 FastAPI 内置 Pydantic 作为数据校验核心)。本教程基于 Pydantic v2(当前稳定主流版本),从基础到进阶,串联所有高频 API,并通过实战案例整合应用。
一、前置准备:安装与核心定位
1. 安装 Pydantic
# 基础安装(推荐)
pip install pydantic
# 完整安装(包含json schema、邮件验证等扩展)
pip install pydantic[email]
2. 核心优势
- 基于 Python 类型注解,语法简洁易读;
- 自动数据验证,内置丰富的校验规则;
- 灵活的序列化/反序列化(支持 dict、JSON、嵌套模型);
- 自定义验证逻辑、字段约束、错误提示;
- 兼容 Python 3.8+,与 FastAPI、Django 等框架无缝集成。
二、核心基础:BaseModel(最核心 API)
Pydantic 所有数据模型都继承自 BaseModel,它是数据验证、序列化/反序列化的核心载体。
1. 定义基础模型
from pydantic import BaseModel, Field
from typing import Optional, List, Literal, Union
import datetime
# 基础用户模型
class User(BaseModel):
# 必选字段:基础类型
id: int
username: str
# 可选字段(默认 None)
email: Optional[str] = None
# 带默认值的字段
age: int = 18
# 枚举类型(仅限指定值)
gender: Literal["male", "female", "other"] = "other"
# 列表类型
hobbies: List[str] = Field(default_factory=list)
# 日期类型(自动解析字符串为 datetime.date)
register_date: datetime.date = Field(default_factory=datetime.date.today)
# 模型配置(v2 推荐用 model_config 类属性)
model_config = {
"str_strip_whitespace": True, # 自动去除字符串首尾空格
"extra": "forbid", # 禁止传入模型未定义的字段(默认 "ignore")
"frozen": False, # 是否冻结模型(冻结后不可修改字段,默认 False)
"populate_by_name": True, # 支持通过字段别名/原名赋值
}
# 实例化模型(自动验证数据)
if __name__ == "__main__":
# 合法数据实例化
user1 = User(
id=1,
username="zhangsan",
email="zhangsan@example.com",
age=20,
gender="male",
hobbies=["reading", "coding"],
register_date="2025-01-01" # 字符串自动解析为 date 类型
)
print("用户1信息:", user1)
# 访问字段(直接属性访问)
print("用户名:", user1.username)
print("年龄:", user1.age)
# 非法数据实例化(触发验证错误)
try:
user2 = User(
id="not_int", # 类型错误:int 期望,传入 str
username=" lisi ", # 自动去除空格
gender="unknown", # 枚举值错误
extra_field="invalid" # 禁止额外字段,触发错误
)
except Exception as e:
print("验证错误:", e)
2. 核心 API:模型实例的基础操作
| API 名称 | 作用(v2 版本) | 示例 |
|---|---|---|
model_dump() |
模型转字典(替代 v1 的 dict()) |
user1.model_dump() |
model_dump_json() |
模型转 JSON 字符串 | user1.model_dump_json(indent=2) |
model_validate() |
从 dict 验证并实例化模型(替代 v1 的 parse_obj()) |
User.model_validate({"id":2, "username":"lisi"}) |
model_validate_json() |
从 JSON 字符串验证并实例化 | User.model_validate_json('{"id":2, "username":"lisi"}') |
model_copy() |
复制模型实例(支持修改字段) | user2 = user1.model_copy(update={"age":21}) |
model_update() |
部分更新模型字段(v2 新增) | user1.model_update({"age":22}) |
# 续上例:模型序列化/反序列化
if __name__ == "__main__":
# 1. 模型转字典
user_dict = user1.model_dump()
print("模型转字典:", user_dict)
# 2. 模型转 JSON(自动处理日期类型为字符串)
user_json = user1.model_dump_json(indent=2)
print("模型转 JSON:", user_json)
# 3. 从字典验证实例化
user2 = User.model_validate({
"id": 2,
"username": "lisi",
"age": 25,
"gender": "female",
"hobbies": ["swimming"],
"register_date": datetime.date(2025, 2, 1)
})
print("从字典实例化:", user2)
# 4. 从 JSON 字符串实例化
user3 = User.model_validate_json(user_json)
print("从 JSON 实例化:", user3)
# 5. 复制模型并修改字段
user4 = user1.model_copy(update={"id": 4, "age": 23})
print("复制并修改字段:", user4)
# 6. 部分更新字段
user1.model_update({"age": 21, "hobbies": ["reading", "running"]})
print("更新后年龄:", user1.age)
三、字段约束:Field 与类型扩展
Field 是 Pydantic 定义字段约束的核心 API,支持自定义字段别名、描述、数值范围、长度限制等。
1. Field 常用参数
| 参数 | 作用 | 示例 |
|---|---|---|
alias |
字段别名(赋值/序列化时可使用) | email: str = Field(alias="user_email") |
gt/lt/ge/le |
数值约束(大于/小于/大于等于/小于等于) | age: int = Field(gt=0, le=120) |
min_length/max_length |
字符串长度约束 | username: str = Field(min_length=3, max_length=20) |
description |
字段描述(生成 JSON Schema 时用) | id: int = Field(description="用户唯一ID") |
default_factory |
动态默认值(避免可变默认值陷阱) | hobbies: List[str] = Field(default_factory=list) |
pattern |
正则表达式约束(字符串字段) | phone: str = Field(pattern=r"^1[3-9]\d{9}$") |
2. 示例:带字段约束的模型
class UserWithConstraint(BaseModel):
id: int = Field(gt=0, description="用户ID,必须大于0")
username: str = Field(
min_length=3,
max_length=20,
pattern=r"^[a-zA-Z0-9_]+$",
description="用户名:3-20位,仅字母/数字/下划线"
)
phone: Optional[str] = Field(
None,
pattern=r"^1[3-9]\d{9}$",
description="手机号:11位数字,以13-9开头"
)
height: float = Field(ge=0.5, le=2.5, description="身高(米):0.5-2.5")
# 别名示例
user_email: str = Field(alias="email", description="用户邮箱")
model_config = {"populate_by_name": True} # 支持别名/原名赋值
if __name__ == "__main__":
# 合法数据
user_constraint = UserWithConstraint(
id=1,
username="zhangsan_123",
phone="13812345678",
height=1.75,
email="zhangsan@example.com" # 通过别名赋值
)
print("带约束的用户:", user_constraint)
# 非法数据(触发验证错误)
try:
UserWithConstraint(
id=0, # 小于等于0,违反 gt=0
username="zs", # 长度不足3
phone="12345678901", # 手机号格式错误
height=3.0, # 超过2.5
user_email="invalid-email" # 邮箱格式错误(Pydantic 内置邮箱验证)
)
except Exception as e:
print("字段约束验证错误:", e)
四、自定义验证:field_validator(核心扩展 API)
当内置约束无法满足需求时,使用 @field_validator 自定义字段验证逻辑(v2 替代 v1 的 @validator)。
1. 单字段验证
from pydantic import field_validator, ValidationError
class UserWithValidator(BaseModel):
username: str
age: int
email: Optional[str] = None
# 验证用户名:不能包含空格
@field_validator("username")
def username_no_space(cls, v):
if " " in v:
raise ValueError("用户名不能包含空格")
return v
# 验证年龄:必须大于等于18(成年)
@field_validator("age")
def age_adult(cls, v):
if v < 18:
raise ValueError("年龄必须大于等于18")
return v
# 验证邮箱:如果提供,必须包含 @
@field_validator("email")
def email_validate(cls, v):
if v is not None and "@" not in v:
raise ValueError("邮箱格式错误,必须包含 @")
return v
if __name__ == "__main__":
# 合法数据
user_valid = UserWithValidator(
username="zhangsan",
age=20,
email="zhangsan@example.com"
)
print("自定义验证通过:", user_valid)
# 非法数据
try:
UserWithValidator(
username="zhang san", # 包含空格
age=17, # 小于18
email="zhangsan.example.com" # 无 @
)
except ValidationError as e:
# 格式化错误信息
print("自定义验证错误详情:")
print(e.json(indent=2))
2. 多字段验证 & 前置验证(pre=True)
pre=True:验证逻辑在 Pydantic 内置验证之前执行(适合处理原始输入);- 多字段验证:参数传入多个字段名,或用
*匹配所有字段。
class Order(BaseModel):
total_amount: float
discount: float
final_amount: float
# 前置验证:将字符串类型的金额转为浮点数(原始输入处理)
@field_validator("total_amount", "discount", "final_amount", pre=True)
def str_to_float(cls, v):
if isinstance(v, str):
try:
return float(v)
except ValueError:
raise ValueError(f"无法将 {v} 转换为浮点数")
return v
# 多字段验证:最终金额 = 总金额 - 折扣,且折扣不能大于总金额
@field_validator("final_amount")
def final_amount_check(cls, v, values, **kwargs):
# values 是已验证的字段(注意顺序:先验证的字段先出现在 values 中)
total = values.data.get("total_amount")
discount = values.data.get("discount")
if total is None or discount is None:
return v # 其他字段验证失败时,跳过当前验证
if discount > total:
raise ValueError("折扣不能大于总金额")
if v != total - discount:
raise ValueError(f"最终金额错误,应为 {total - discount},实际为 {v}")
return v
if __name__ == "__main__":
# 合法数据(字符串自动转浮点数)
order1 = Order(
total_amount="100.0",
discount="20.0",
final_amount="80.0"
)
print("订单验证通过:", order1)
# 非法数据
try:
Order(
total_amount="100",
discount="150", # 折扣大于总金额
final_amount="-50"
)
except ValidationError as e:
print("多字段验证错误:", e)
五、进阶用法:嵌套模型 & 自定义类型
1. 嵌套模型(高频场景:复杂数据结构)
# 地址模型(嵌套子模型)
class Address(BaseModel):
province: str
city: str
detail: str
zip_code: Optional[str] = None
# 包含嵌套模型的用户模型
class UserWithAddress(BaseModel):
id: int
username: str
# 嵌套单个模型
default_address: Address
# 嵌套模型列表
other_addresses: List[Address] = Field(default_factory=list)
if __name__ == "__main__":
# 实例化嵌套模型
user_address = UserWithAddress(
id=1,
username="zhangsan",
default_address={
"province": "北京市",
"city": "北京市",
"detail": "朝阳区XX街道",
"zip_code": "100000"
},
other_addresses=[
{
"province": "上海市",
"city": "上海市",
"detail": "浦东新区XX路"
}
]
)
print("嵌套模型转JSON:", user_address.model_dump_json(indent=2))
# 访问嵌套字段
print("默认地址城市:", user_address.default_address.city)
2. 自定义类型(复用验证逻辑)
当多个模型需要相同的字段验证规则时,自定义类型可以避免重复代码。
from pydantic import BaseModel, GetCoreSchemaHandler
from pydantic_core import CoreSchema, core_schema
from typing import Any
# 自定义手机号类型
class PhoneNumber(str):
@classmethod
def __get_pydantic_core_schema__(
cls,
source_type: Any,
handler: GetCoreSchemaHandler
) -> CoreSchema:
# 定义核心验证逻辑
return core_schema.no_info_wrap_validator_function(
cls.validate, # 验证函数
core_schema.str_schema(), # 基础类型为 str
serialization=core_schema.to_string_ser_schema(),
)
@classmethod
def validate(cls, v: str) -> str:
if not v.startswith("1") or len(v) != 11 or not v.isdigit():
raise ValueError("手机号必须是11位数字,且以1开头")
return v
# 使用自定义类型
class UserWithCustomType(BaseModel):
username: str
phone: PhoneNumber # 自定义手机号类型
if __name__ == "__main__":
# 合法数据
user_custom = UserWithCustomType(
username="zhangsan",
phone="13812345678"
)
print("自定义类型验证通过:", user_custom)
# 非法数据
try:
UserWithCustomType(
username="zhangsan",
phone="1234567890" # 长度不足11
)
except ValidationError as e:
print("自定义类型验证错误:", e)
六、实战案例:完整用户数据处理流程
整合以上所有常用 API,实现「用户注册数据验证 → 序列化存储 → 反序列化读取 → 数据更新校验 → 嵌套地址验证」的完整流程。
from pydantic import BaseModel, Field, field_validator, ValidationError
from typing import Optional, List
import datetime
import json
# 1. 定义嵌套模型:地址
class Address(BaseModel):
province: str
city: str
detail: str
zip_code: Optional[str] = Field(None, pattern=r"^\d{6}$", description="邮编为6位数字")
@field_validator("province", "city")
def address_not_empty(cls, v):
if not v.strip():
raise ValueError("省份/城市不能为空")
return v
# 2. 定义用户主模型(包含所有常用约束、验证)
class User(BaseModel):
id: int = Field(gt=0, description="用户ID必须大于0")
username: str = Field(
min_length=3,
max_length=20,
pattern=r"^[a-zA-Z0-9_]+$",
description="用户名:3-20位,仅字母/数字/下划线"
)
email: Optional[str] = Field(None, description="用户邮箱")
age: int = Field(default=18, gt=0, le=120, description="年龄:0-120岁")
gender: Optional[str] = Field(None, pattern=r"^(male|female|other)$")
phone: Optional[str] = Field(None, pattern=r"^1[3-9]\d{9}$", description="手机号")
register_date: datetime.date = Field(default_factory=datetime.date.today)
default_address: Address
hobbies: List[str] = Field(default_factory=list)
# 模型配置
model_config = {
"str_strip_whitespace": True,
"extra": "forbid",
"populate_by_name": True,
}
# 自定义验证器:用户名不能包含敏感词
@field_validator("username")
def username_no_sensitive(cls, v):
sensitive_words = ["admin", "root", "superuser"]
if v.lower() in sensitive_words:
raise ValueError(f"用户名不能包含敏感词:{sensitive_words}")
return v
# 自定义验证器:邮箱格式(如果提供)
@field_validator("email")
def email_validate(cls, v):
if v and "@" not in v:
raise ValueError("邮箱格式错误,必须包含 @")
return v
# 3. 实战流程:注册 → 存储 → 读取 → 更新 → 校验
if __name__ == "__main__":
# 步骤1:用户注册数据(模拟前端传入的原始数据)
raw_user_data = {
"id": 1001,
"username": "zhangsan_888",
"email": "zhangsan@example.com",
"age": 25,
"gender": "male",
"phone": "13912345678",
"register_date": "2025-01-01",
"default_address": {
"province": "广东省",
"city": "深圳市",
"detail": "南山区XX大厦1001室",
"zip_code": "518000"
},
"hobbies": ["reading", "coding", "travel"]
}
try:
# 步骤2:验证并实例化用户模型
user = User.model_validate(raw_user_data)
print("✅ 用户注册数据验证通过")
# 步骤3:序列化为 JSON 存储(模拟写入数据库/文件)
user_json = user.model_dump_json(indent=2)
with open("user_data.json", "w", encoding="utf-8") as f:
f.write(user_json)
print("✅ 用户数据已序列化存储到 user_data.json")
# 步骤4:从文件读取 JSON 并反序列化(模拟从数据库读取)
with open("user_data.json", "r", encoding="utf-8") as f:
stored_json = f.read()
stored_user = User.model_validate_json(stored_json)
print("✅ 从存储读取并反序列化用户数据:")
print(f" 用户名:{stored_user.username},默认地址:{stored_user.default_address.city}")
# 步骤5:更新用户信息(部分更新)
update_data = {
"age": 26,
"phone": "13888888888",
"default_address": {
"province": "广东省",
"city": "广州市",
"detail": "天河区XX路88号",
"zip_code": "510000"
},
"hobbies": ["reading", "coding", "travel", "cooking"]
}
stored_user.model_update(update_data)
print("✅ 用户信息更新完成")
print(f" 更新后年龄:{stored_user.age},更新后手机号:{stored_user.phone}")
# 步骤6:验证更新后的数据(重新验证)
updated_user = User.model_validate(stored_user.model_dump())
print("✅ 更新后数据重新验证通过")
except ValidationError as e:
print("❌ 数据验证失败:")
print(e.json(indent=2))
七、常用 API 速查表(v2 核心)
| 类别 | API 名称 | 核心用途 |
|---|---|---|
| 模型基类 | BaseModel |
所有数据模型的父类,承载验证/序列化核心逻辑 |
| 字段约束 | Field |
定义字段的约束、别名、默认值、描述等 |
| 数据验证 | field_validator |
自定义字段验证逻辑 |
| 序列化 | model_dump() |
模型转字典 |
model_dump_json() |
模型转 JSON 字符串 | |
| 反序列化 | model_validate() |
从字典验证并实例化模型 |
model_validate_json() |
从 JSON 字符串验证并实例化模型 | |
| 模型操作 | model_copy() |
复制模型实例(支持修改字段) |
model_update() |
部分更新模型字段 | |
| 错误处理 | ValidationError |
捕获并处理验证错误 |
| 模型配置 | model_config |
全局配置模型行为(如去除空格、禁止额外字段) |
八、常见问题与注意事项
- v1 vs v2 差异:v2 重构了底层,
validator→field_validator,dict()→model_dump(),parse_obj()→model_validate(); - 可变默认值陷阱:列表/字典等可变类型的默认值必须用
default_factory(如hobbies: List[str] = Field(default_factory=list)),否则会导致所有实例共享同一个对象; - 字段顺序:多字段验证时,
values.data中仅包含已验证的字段(按模型定义顺序); - 错误提示自定义:在
field_validator中可自定义ValueError提示语,或通过model_config全局配置错误格式; - 与 FastAPI 集成:FastAPI 自动将 Pydantic 模型作为接口请求体/响应体,无需手动调用
model_validate/model_dump。
总结
Pydantic 的核心是「声明式数据验证」,通过 BaseModel 定义结构,Field 约束字段,field_validator 扩展逻辑,再通过 model_dump/model_validate 完成序列化/反序列化。掌握以上 API 后,可轻松处理 Python 中各类数据验证、配置解析、接口参数校验场景,是后端开发必备的核心库之一。
posted on
浙公网安备 33010602011771号