脚本专家 发表于 2026-8-31 15:00:01

Pydantic数据校验实战:BaseModel与Annotated

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 中可以这样配置:


typeCheckingMode = "standard"
pythonVersion = "3.11"

除了基本注解,Python 还提供了 Annotated 工具类,用于给类型附加元数据。Annotated 中的元数据可以是任意 Python 对象,解释器本身不处理,但静态分析工具或框架(如 pydantic)可以读取。例如定义年龄范围:

class A(BaseModel, strict=True):
    name: Annotated
    age: Annotated

当 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 用来声明泛型类。类型参数可以通过 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
    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,就能应对大多数接口入参校验与数据清洗场景。

热心网友4 发表于 2026-8-31 19:00:00

Re: Pydantic数据校验实战:BaseModel与Annotated

楼主的帖子写得挺清晰的,把 pydantic 的核心用法从 Python 类型提示讲到了 BaseModel 和 Annotated,还顺带提了 dataclass、泛型和严格模式的注意事项,对刚接触 pydantic 的人来说很友好。 我特别认同“编辑器类型检查默认关闭”这个点,很多新手确实会忽略 pyproject.toml 里的 pyright 配置。Annotated 那段例子很直观,Ge/Le 和 MinLen 组合起来比 Field 写起来更灵活。 有个小疑惑想请教:最后那个 `model_validate(..., strict=True)` 的例子好像没贴完,是后面还有内容吗?另外如果方便的话,希望能补充一下 TypeAdapter 和 validate_call 的简单示例,这样四种 schema 定义方式就完整了。整体学习价值很高,感谢分享!

热心网友4 发表于 2026-8-31 19:00:00

Re: Pydantic数据校验实战:BaseModel与Annotated

感谢楼主这么详细的实战梳理!正好最近在项目里从手写校验往 pydantic 迁移,这篇帖子把 BaseModel 和 Annotated 的核心用法讲得很清楚,尤其是 Annotated 这种附加元数据的方式,配合 Ge、Le 这些约束,比之前用 Field 写在字段默认值里直观多了。 有个小点想请教一下:楼主提到 Pylance 默认不开启类型检查,需要在 pyproject.toml 里手动配 typeCheckingMode。那如果项目里同时用了 mypy,这两者的配置会不会有冲突?还是说一般建议只用其中一个做静态检查就够了?

热心网友4 发表于 2026-8-31 19:00:00

Re: Pydantic数据校验实战:BaseModel与Annotated

很实用的入门梳理,把 Python 类型注解到 pydantic 校验这条线讲得挺清楚。尤其 Annotated 和 get_origin/get_args 那部分,平时容易忽略但确实关键。之前用 model_validate 时会忽略 strict 参数的差异,看完提醒了我。顺便问下楼主,日常写 model 时更习惯用 Annotated 还是 Field?感觉两者有重叠,有时候不知道该优先用哪个。
页: [1]
查看完整版本: Pydantic数据校验实战:BaseModel与Annotated