查看: 1269|回复: 3

Pydantic数据校验实战:BaseModel与Annotated

[复制链接]
发表于 昨天 15:00 | 显示全部楼层 |阅读模式
pydantic 是基于 Python 类型提示(type hints)的数据校验库,官方文档见 https://docs.pydantic.dev/latest。它用类型注解定义数据 schema,在运行时完成校验与序列化。本文从 Python 类型系统的前置知识出发,梳理 pydantic 的常规用法,重点演示 BaseModel、Annotated、model_validate 等实战场景。

Python 是弱类型语言,解释器不会在运行时做类型检查。比如下面这个函数,参数本意是字符串,如果传入整数,只有运行到 split 时才会抛异常:
  1. def test_str(a):
  2.     return a.split(' ')
复制代码
给参数加上类型注解后,配合 VSCode 的 Pylance(基于 Pyright)插件,可以在编辑阶段发现类型不匹配:
  1. def test_str(a: str):
  2.     return a.split(' ')
复制代码
但 Pylance 的类型检查默认是关闭的,需要开启。在项目的 pyproject.toml 中可以这样配置:
  1. [tool.pyright]
  2. typeCheckingMode = "standard"
  3. pythonVersion = "3.11"
复制代码
除了基本注解,Python 还提供了 Annotated 工具类,用于给类型附加元数据。Annotated[T, meta1, meta2] 中的元数据可以是任意 Python 对象,解释器本身不处理,但静态分析工具或框架(如 pydantic)可以读取。例如定义年龄范围:
  1. class A(BaseModel, strict=True):
  2.     name: Annotated[str, MinLen(5)]
  3.     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__ 等魔术方法。注意,没有类型注解的类属性不会被当作字段:
  1. @dataclass
  2. class B:
  3.     name: str = 'abc'
  4.     age = 10  # 不会出现在 __init__ 中
复制代码
实例化后 b.__dict__ 只包含 name,不包含 age。

pydantic 还依赖一部分泛型知识。TypeVar 是类型变量占位符,Generic[T] 用来声明泛型类。类型参数可以通过 bound 设置上界,或者通过 constraints 限制为几个类型之一。需要注意的是,bound 和 constraints 互斥。例如:
  1. T_bound = TypeVar('T_bound', bound=TBound)
  2. T_cons = TypeVar('T_cons', str, float)
复制代码
泛型方法中,如果方法使用与类绑定的类型变量之外的新类型变量,也属于泛型方法。

进入 pydantic 正题。pydantic 的 schema 校验基于 Python 类型提示,定义 schema 有四种方式:BaseModel 数据模型、pydantic.dataclasses.dataclass 装饰器、TypeAdapter、validate_call。本文重点介绍 BaseModel。

继承自 BaseModel 的类就是一个数据模型类,类属性就是字段,字段必须带类型注解。通过 ConfigDict(strict=True) 可以开启严格模式,此时字段值必须与类型完全匹配,不做隐式转换:
  1. from pydantic import BaseModel, ConfigDict, ValidationError
  2. from annotated_types import Le, Ge
  3. class A(BaseModel):
  4.     name: str
  5.     age: int
  6.     model_config = ConfigDict(strict=True)
  7. try:
  8.     a = A(name='abc', age='11')
  9. except ValidationError as e:
  10.     print(e)
  11. a = A(name='abc', age=10)
  12. print(a.model_dump())
复制代码
输出中会给出 age 字段的详细错误信息,包括错误类型和输入值。严格模式下字符串 '11' 不会被转换为整数,而是直接报错。

模型实例也支持拷贝。model_copy 可以执行浅拷贝或深拷贝,并且可以在拷贝时更新字段:
  1. copy_obj = obj.model_copy(update={'arg2': 3.4})
  2. deep_copy_obj = obj.model_copy(deep=True)
复制代码
浅拷贝时内部嵌套模型对象是同一个引用;深拷贝则完全独立。

pydantic 还提供了三个校验相关类方法:model_validate、model_validate_json、model_validate_strings。model_validate 接收一个已存在的 Python 字典或模型实例,校验后返回模型对象。例如:
  1. class B(BaseModel):
  2.     name: str = Field(default='abc', min_length=1)
  3.     age: Annotated[int, Ge(0), Le(100)]
  4.     sign_time: datetime | None = None
  5. b = B.model_validate({'name': '123', 'age': '20', 'sign_time': '2025-10-09T22:00:00Z'})
  6. print(b)
复制代码
默认模式下,字符串形式的 age 和 sign_time 会被解析成对应类型。如果调用时传入 strict=True,则要求类型完全匹配,字符串 '20' 就会报错:
  1. 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,就能应对大多数接口入参校验与数据清洗场景。
回复

使用道具 举报

发表于 昨天 19: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 定义方式就完整了。整体学习价值很高,感谢分享!
回复 支持 反对

使用道具 举报

发表于 昨天 19:00 | 显示全部楼层

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

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

使用道具 举报

发表于 昨天 19:00 | 显示全部楼层

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

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

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-9-1 02:00 , Processed in 0.023772 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部