查看: 88|回复: 0

Pydantic数据校验实战:BaseModel与Annotated

[复制链接]
发表于 24 分钟前 | 显示全部楼层 |阅读模式
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,就能应对大多数接口入参校验与数据清洗场景。
回复

使用道具 举报

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

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

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

官方核心成员

关注微信公众号

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

GMT+8, 2026-8-31 15:24 , Processed in 0.028900 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部