pydantic 是基于 Python 类型提示(type hints)的数据校验库,官方文档见 https://docs.pydantic.dev/latest。它用类型注解定义数据 schema,在运行时完成校验与序列化。本文从 Python 类型系统的前置知识出发,梳理 pydantic 的常规用法,重点演示 BaseModel、Annotated、model_validate 等实战场景。
Python 是弱类型语言,解释器不会在运行时做类型检查。比如下面这个函数,参数本意是字符串,如果传入整数,只有运行到 split 时才会抛异常:- def test_str(a):
- return a.split(' ')
复制代码 给参数加上类型注解后,配合 VSCode 的 Pylance(基于 Pyright)插件,可以在编辑阶段发现类型不匹配:- def test_str(a: str):
- return a.split(' ')
复制代码 但 Pylance 的类型检查默认是关闭的,需要开启。在项目的 pyproject.toml 中可以这样配置:- [tool.pyright]
- typeCheckingMode = "standard"
- pythonVersion = "3.11"
复制代码 除了基本注解,Python 还提供了 Annotated 工具类,用于给类型附加元数据。Annotated[T, meta1, meta2] 中的元数据可以是任意 Python 对象,解释器本身不处理,但静态分析工具或框架(如 pydantic)可以读取。例如定义年龄范围:- class A(BaseModel, strict=True):
- name: Annotated[str, MinLen(5)]
- age: Annotated[int, Ge(0), Le(100)]
复制代码 当 name 过短或 age 超过 100 时,pydantic 会在实例化时抛 ValidationError。这就是 Annotated 在 pydantic 中的核心价值。
与 Annotated 相关的三个内省函数分别是 get_origin、get_args 和 get_type_hints。get_origin 返回类型构造器,get_args 返回类型参数元组,get_type_hints 在 include_extras=True 时能保留完整的 Annotated 信息。
接着是 dataclasses.dataclass。这个装饰器可以根据类中带类型注解的属性自动生成 __init__、__repr__、__eq__ 等魔术方法。注意,没有类型注解的类属性不会被当作字段:- @dataclass
- class B:
- name: str = 'abc'
- age = 10 # 不会出现在 __init__ 中
复制代码 实例化后 b.__dict__ 只包含 name,不包含 age。
pydantic 还依赖一部分泛型知识。TypeVar 是类型变量占位符,Generic[T] 用来声明泛型类。类型参数可以通过 bound 设置上界,或者通过 constraints 限制为几个类型之一。需要注意的是,bound 和 constraints 互斥。例如:- T_bound = TypeVar('T_bound', bound=TBound)
- T_cons = TypeVar('T_cons', str, float)
复制代码 泛型方法中,如果方法使用与类绑定的类型变量之外的新类型变量,也属于泛型方法。
进入 pydantic 正题。pydantic 的 schema 校验基于 Python 类型提示,定义 schema 有四种方式:BaseModel 数据模型、pydantic.dataclasses.dataclass 装饰器、TypeAdapter、validate_call。本文重点介绍 BaseModel。
继承自 BaseModel 的类就是一个数据模型类,类属性就是字段,字段必须带类型注解。通过 ConfigDict(strict=True) 可以开启严格模式,此时字段值必须与类型完全匹配,不做隐式转换:- from pydantic import BaseModel, ConfigDict, ValidationError
- from annotated_types import Le, Ge
- class A(BaseModel):
- name: str
- age: int
- model_config = ConfigDict(strict=True)
- try:
- a = A(name='abc', age='11')
- except ValidationError as e:
- print(e)
- a = A(name='abc', age=10)
- print(a.model_dump())
复制代码 输出中会给出 age 字段的详细错误信息,包括错误类型和输入值。严格模式下字符串 '11' 不会被转换为整数,而是直接报错。
模型实例也支持拷贝。model_copy 可以执行浅拷贝或深拷贝,并且可以在拷贝时更新字段:- copy_obj = obj.model_copy(update={'arg2': 3.4})
- deep_copy_obj = obj.model_copy(deep=True)
复制代码 浅拷贝时内部嵌套模型对象是同一个引用;深拷贝则完全独立。
pydantic 还提供了三个校验相关类方法:model_validate、model_validate_json、model_validate_strings。model_validate 接收一个已存在的 Python 字典或模型实例,校验后返回模型对象。例如:- class B(BaseModel):
- name: str = Field(default='abc', min_length=1)
- age: Annotated[int, Ge(0), Le(100)]
- sign_time: datetime | None = None
- b = B.model_validate({'name': '123', 'age': '20', 'sign_time': '2025-10-09T22:00:00Z'})
- print(b)
复制代码 默认模式下,字符串形式的 age 和 sign_time 会被解析成对应类型。如果调用时传入 strict=True,则要求类型完全匹配,字符串 '20' 就会报错:- b1 = B.model_validate({'name': '123', 'age': '20', 'sign_time': '2025-10-09T22:00:00Z'}, strict=True)
复制代码 这时只有使用真正的 int 和 datetime 对象才能通过校验。
以上就是 pydantic 结合 Annotated、dataclass、泛型等 Python 类型系统特性进行数据校验的实战总结。理解这些前置知识,再使用 BaseModel 和 model_validate,就能应对大多数接口入参校验与数据清洗场景。 |