ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Pydantic V2 双引擎架构深度解析:Python 与 Rust(pydantic-core)如何协同完成校验与序列化

Pydantic V2 双引擎架构深度解析:Python 与 Rust(pydantic-core)如何协同完成校验与序列化 Pydantic V2 双引擎架构深度解析Python 与 Rustpydantic-core如何协同完成校验与序列化【免费下载链接】pydanticData validation using Python type hints项目地址: https://gitcode.com/GitHub_Trending/py/pydanticPydantic V2 对内部架构进行了根本性重构核心的校验与序列化逻辑被用 Rust 重写独立为pydantic-core包而 Python 侧的pydantic包负责模型定义。本文基于仓库内 docs/internals/architecture.md面向贡献者的 Internals 文档完整梳理这条「模型定义Python→ 核心 schema → 校验与序列化Rust」的调用链路并深入pydantic、pydantic-core两包的源码与测试帮助你理解BaseModel从类定义到实例校验的全过程掌握 core schema、GenerateSchema、GenerateJsonSchema、SchemaValidator/SchemaSerializer以及插件钩子的实现原理。读完本文你将具备阅读 Pydantic 源码、编写自定义类型与插件的底层知识。双包架构总览一次定义两端分工从 Pydantic V2 开始代码库被划分为两个包包语言职责pydanticPython模型定义Model definition收集字段、配置、验证器/序列化器pydantic-coreRust模型校验与序列化Validation serialization按 core schema 执行这种拆分的主要动机是提升校验与序列化性能文档明示相对 Pydantic V1 有5 到 20 倍的性能提升代价是内部逻辑的可定制性与可扩展性受到限制——例如核心校验逻辑无法在 Python 侧直接改写。使用 Pydantic 的行为因此可以划分为两个阶段模型定义在pydantic包中完成作用于类class级别发生在定义模型类时模型校验与序列化在pydantic-core包中完成作用于实例instance级别发生在model_validate()/model_dump()等调用时。两个阶段之间的翻译官就是贯穿全文的核心概念——core schema。模型定义阶段元类如何解剖一个模型类每当定义一个BaseModel子类其元类metaclass会分析类体收集以下元素对应源码见 pydantic/_internal/_model_construction.py类型注解 → 构建模型字段收集到model_fields属性通过model_config设置的模型配置额外的验证器field_validator、model_validator与序列化器field_serializer、model_serializer私有属性、类变量ClassVar、泛型参数化识别等。收集完毕后元类会调用 schema 生成逻辑最终把成果固化在类属性__pydantic_core_schema__上。从源码看pydantic/_internal/_model_construction.py#L621-L661这一过程依次完成实例化GenerateSchema(config_wrapper, ns_resolver, typevars_map)调用gen_schema.generate_schema(cls)生成 core schema调用gen_schema.clean_schema(schema)清理并收集递归定义将结果写入cls.__pydantic_core_schema__ schema通过create_schema_validator(...)创建__pydantic_validator__并创建__pydantic_serializer__ SchemaSerializer(schema, core_config, ...)。也就是说模型类在定义完成的那一刻就已经把校验器和序列化器都编译好了后续实例化与校验不再需要 Python 侧的重复分析。核心 schemaPython 与 Rust 之间的通信语言什么是 core schemapydanticPython需要把模型定义阶段收集到的信息传递给pydantic-coreRust使其按预期执行校验与序列化。这个通信载体就是core schema一种结构化、可序列化的 Python 字典其类型定义使用TypedDict描述见 pydantic-core/python/pydantic_core/core_schema.py。core schema 具有以下特性每个 schema 必须包含type键其余键随type不同而变化它是pydantic与pydantic-core两包之间唯一的、统一的数据结构核心 schema 的生成统一由GenerateSchema类负责pydantic/_internal/_generate_schema.py#L321无论目标是 Pydantic 模型、dataclass 还是str、datetime等普通类型不允许自定义 core schema由于pydantic-core只能理解固定数量的 schema 类型GenerateSchema没有对外暴露也没有正式文档——这也是其设计上的一大约束。用bool字段看懂 core schema 长什么样以bool_schema为例其在pydantic_core.core_schema中的TypedDict定义为class BoolSchema(TypedDict, totalFalse): type: Required[Literal[bool]] strict: bool ref: str metadata: Any serialization: SerSchema对应源码见 pydantic-core/python/pydantic_core/core_schema.py#L607-L638其中bool_schema()工厂函数会通过_dict_not_none(typebool, strict..., ref..., metadata..., serialization...)构造字典只保留非None的键。当我们定义一个带布尔字段的模型from pydantic import BaseModel, Field class Model(BaseModel): foo: bool Field(strictTrue)foo字段的 core schema 就形如{ type: bool, strict: True, }注意type键是必需且唯一的标识strict则来自Field(strictTrue)。从 pydantic/_internal/_generate_schema.py#L349-L405 可以看到GenerateSchema._handlers是一张内置类型 → schema 工厂的映射表例如bool: lambda self, obj: core_schema.bool_schema()、int: lambda self, obj: core_schema.int_schema()这解释了为何每个内置类型都能快速得到对应的基础 schema。serialization 也是 schema 的一部分正如BoolSchema定义所示序列化逻辑同样内嵌在 core schema 中。若用field_serializer为foo定义自定义序列化函数class Model(BaseModel): foo: bool Field(strictTrue) field_serializer(foo, modeplain) def serialize_foo(self, value: bool) - Any: ...则serialization键会变成{ type: function-plain, function: function Model.serialize_foo at 0x111, is_field_serializer: True, info_arg: False, return_schema: {type: int}, }这本身也是一个 core schema——它只在pydantic-core执行序列化时生效。注意其中的return_schema: {type: int}这正是 core schema 同时服务于 JSON Schema 生成的证据我们会在下一节展开。从设计意图看core schema 的覆盖范围远不止校验与序列化只要是 Python 与 Rust 两侧需要通信的信息错误管理、额外元数据等理论上都可以用它承载。JSON Schema 生成复用同一份 core schemaGenerateJsonSchema类pydantic/json_schema.py#L223专门负责把 core schema 翻译成 JSON Schema。其generate方法源码见 pydantic/json_schema.py#L399是主入口入参就是模型的 core schema。几个值得注意的默认行为源码见 pydantic/json_schema.py#L265-L279schema_dialect https://json-schema.org/draft/2020-12/schema默认使用 JSON Schema 2020-12 草案by_alias: bool True生成 schema 时默认使用字段别名ref_template控制引用名的格式union_format支持any_of默认与primitive_type_array两种联合类型表达方式。回到bool字段的例子GenerateJsonSchema.bool_schema方法拿到前一步生成的布尔 core schema 后会返回{ {type: boolean} }注原文此处为文档笔误实际返回应为{type: boolean}即一个以type: boolean为键值的 JSON Schema 片段。return_schema、strict等 core schema 中的约束都会在这一步被翻译为 JSON Schema 的对应关键字。自定义 core schema 与 JSON schema包装模式wrapper patternGenerateSchema与GenerateJsonSchema负责创建 schema但 Pydantic 也提供了有限的定制入口——通过__get_pydantic_core_schema__与__get_pydantic_json_schema__两个方法遵循一种包装模式先调用 handler 拿到默认 schema再就地修改后返回。用法文档见自定义类型 Custom types实现__get_pydantic_core_schema__实现__get_pydantic_json_schema__借助Annotated元数据理解包装模式原文以配合Annotated使用的元数据类为例__get_pydantic_core_schema__可以这样实现from typing import Annotated, Any from pydantic_core import CoreSchema from pydantic import GetCoreSchemaHandler, TypeAdapter class MyStrict: classmethod def __get_pydantic_core_schema__( cls, source: Any, handler: GetCoreSchemaHandler ) - CoreSchema: schema handler(source) # (1)! schema[strict] True return schema class MyGt: classmethod def __get_pydantic_core_schema__( cls, source: Any, handler: GetCoreSchemaHandler ) - CoreSchema: schema handler(source) # (2)! schema[gt] 1 return schema ta TypeAdapter(Annotated[int, MyStrict(), MyGt()])执行过程中的 schema 演变MyStrict是第一个被应用的注解。此刻schema {type: int}随后被加上strict: TrueMyGt是最后一个被应用的注解。此刻schema {type: int, strict: True}随后被加上gt: 1。其底层原理是当GenerateSchema为Annotated[int, MyStrict(), MyGt()]构建 core schema 时会创建一个GetCoreSchemaHandler实例传给MyGt.__get_pydantic_core_schema__。在Annotated场景下这个 handler 是嵌套定义的调用它会递归地触发其他__get_pydantic_core_schema__方法直到抵达int注解本身返回最基础的{type: int}schema。source参数的值取决于 core schema 的生成模式对于Annotated模式source是被注解的类型本身对于在自定义类型上以方法形式定义的模式source是定义__get_pydantic_core_schema__的那个类。与GenerateSchema对应的__get_pydantic_json_schema__遵循完全相同的包装模式只是作用于 JSON Schema 生成阶段handler 类型为GetJsonSchemaHandler。实例级别SchemaValidator与SchemaSerializer如何执行模型定义完成后校验与序列化在实例级别由pydantic-core完成。pydantic-core对外暴露两个核心类SchemaValidator执行校验SchemaSerializer执行序列化。典型调用from pydantic import BaseModel class Model(BaseModel): foo: int model Model.model_validate({foo: 1}) # (1)! dumped model.model_dump() # (2)!输入数据通过SchemaValidator.validate_python被送到pydantic-coreRust 侧按模型的 core schema 校验数据并直接填充模型的__dict__属性实例通过SchemaSerializer.to_python被送到pydantic-coreRust 侧读取实例的__dict__同样按 core schema 构建出相应结果。换句话说model_validate/model_dump本质上是把数据交给 Rust 侧再由 Rust 侧读写 Python 对象的__dict__——这也是性能提升的主要来源之一。对应的 Rust 校验器实现散落在 pydantic-core/src/validators 目录如int.rs、string.rs、model.rs、typed_dict.rs等每个文件对应一类 core schema 的执行逻辑。插件钩子在不改动核心的情况下观测每一次校验pydantic在SchemaValidator外包了一层插件层。当插件被安装后validate_python、validate_json、validate_strings三个方法都可能被拦截使插件能观察到每一次校验的输入、结果以及任何错误——而无需逐调用点埋点。其实现位于 pydantic/plugin/_schema_validator.pyPluggableSchemaValidator类第 56 行起持有真实的_schema_validator一个 RustSchemaValidator实例构造时遍历所有已注册插件调用plugin.new_schema_validator(...)收集事件处理器通过build_wrapper(...)把validate_python/validate_json/validate_strings分别包装成先触发事件处理器、再调用底层 Rust 校验器的版本见第 91-93 行。这正是 Logfire 等可观测性工具的实现机制它借助该插件钩子记录每次校验无需对应用代码做任何逐调用点插桩。插件是按模型配置的通过plugin_settings配置项设置源码定义见 pydantic/config.py#L811并在模型构建时由config_wrapper.plugin_settings传入create_schema_validator见 pydantic/_internal/_model_construction.py#L651-L660。这意味着不同模型可以启用不同的插件集合插件行为是可控、可隔离的。架构源码地图从文档到代码的快速导航架构环节关键类 / 属性源码位置模型定义元类收集BaseModel元类、model_fields、model_configpydantic/_internal/_model_construction.pycore schema 生成GenerateSchema、__pydantic_core_schema__pydantic/_internal/_generate_schema.py#L321、pydantic-core/python/pydantic_core/core_schema.pyJSON Schema 生成GenerateJsonSchema.generatepydantic/json_schema.py#L223、pydantic/json_schema.py#L399校验 / 序列化执行SchemaValidator、SchemaSerializerpydantic-core/src/validators、pydantic-core/src/serializers插件钩子PluggableSchemaValidator、plugin_settingspydantic/plugin/_schema_validator.py、pydantic/config.py#L811如果希望进一步深入docs/internals/resolving_annotations.md 详细讲解了注解解析forward reference 与命名空间解析的机制这是理解GenerateSchema中NsResolver如何工作的前提而 docs/concepts/types.md 与 docs/concepts/json_schema.md 则从使用侧覆盖了本文提到的自定义类型与 schema 定制能力。小结理解 Pydantic V2 架构的关键是记住一条单向数据流Python 侧定义模型 → 元类借助GenerateSchema产出 core schema →__pydantic_core_schema__携带全部信息 → Rust 侧SchemaValidator/SchemaSerializer按 schema 执行校验与序列化而 JSON Schema 只是这份 core schema 的另一种渲染。在此基础上__get_pydantic_core_schema__/__get_pydantic_json_schema__提供了有限的包装式定制入口插件钩子则让可观测性工具能在不改核心逻辑的前提下捕获每一次校验。掌握这条链路无论是排查校验行为、编写自定义类型还是为 Pydantic 贡献代码都有了清晰的地图。【免费下载链接】pydanticData validation using Python type hints项目地址: https://gitcode.com/GitHub_Trending/py/pydantic创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表