大家好,我是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 全局配置模型行为(如去除空格、禁止额外字段)

八、常见问题与注意事项

  1. v1 vs v2 差异:v2 重构了底层,validatorfield_validatordict()model_dump()parse_obj()model_validate()
  2. 可变默认值陷阱:列表/字典等可变类型的默认值必须用 default_factory(如 hobbies: List[str] = Field(default_factory=list)),否则会导致所有实例共享同一个对象;
  3. 字段顺序:多字段验证时,values.data 中仅包含已验证的字段(按模型定义顺序);
  4. 错误提示自定义:在 field_validator 中可自定义 ValueError 提示语,或通过 model_config 全局配置错误格式;
  5. 与 FastAPI 集成:FastAPI 自动将 Pydantic 模型作为接口请求体/响应体,无需手动调用 model_validate/model_dump

总结

Pydantic 的核心是「声明式数据验证」,通过 BaseModel 定义结构,Field 约束字段,field_validator 扩展逻辑,再通过 model_dump/model_validate 完成序列化/反序列化。掌握以上 API 后,可轻松处理 Python 中各类数据验证、配置解析、接口参数校验场景,是后端开发必备的核心库之一。

 posted on 2025-12-06 15:10  蒙蒙01  阅读(0)  评论(0)    收藏  举报