十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

pydantic 动态模型创建实战:基于 `create_model()` 派生可选字段模型

pydantic 动态模型创建实战:基于 `create_model()` 派生可选字段模型 pydantic 动态模型创建实战基于create_model()派生可选字段模型【免费下载链接】pydanticData validation using Python type hints项目地址: https://gitcode.com/GitHub_Trending/py/pydantic导读在数据校验场景中我们经常需要根据运行时信息动态生成模型例如把现有模型的全部字段派生为可选字段用于部分更新等场景。本篇文章以 pydantic 官方示例 docs/examples/dynamic_models.md 为骨架深入讲解如何借助create_model()工厂函数、model_fields与FieldInfo.asdict()从已有模型动态派生新模型并给出完整可运行的代码、Python 3.10 / 3.11 两种写法、类型标注技巧与实验性MISSING哨兵方案。读完本文你将掌握一套可复制的字段可选化工厂函数实现并理解 pydantic 动态模型创建的底层机制。一、场景与思路为什么要动态派生模型Pydantic 支持通过create_model()工厂函数在运行时动态创建模型。官方文档 docs/concepts/models.md 给出了最基础的用法from pydantic import BaseModel, create_model DynamicFoobarModel create_model(DynamicFoobarModel, foostr, bar(int, 123)) # 等价于 class StaticFoobarModel(BaseModel): foo: str bar: int 123字段定义以关键字参数传入可以是两种形式单元素仅表示字段的类型注解例如foostr二元组第一个元素是类型第二个元素是赋值默认值或Field()函数调用例如bar(int, 123)。在此基础上create_model()还支持一系列以双下划线开头的特殊关键字参数用于定制新模型的行为对应源码 pydantic/main.py 中的函数签名参数说明__config__新模型的ConfigDict配置与__base__互斥同时传入会抛出PydanticUserError__doc__新模型的 docstring__base__新模型的基类或基类元组__module__新模型所属模块名缺省时取自调用方栈帧__validators__字段校验器字典键为校验器方法名值为校验器函数__cls_kwargs__类创建时的额外关键字参数如metaclass__qualname__新模型的限定名**field_definitions字段定义单元素或二元组注意根据 pydantic/main.py 中的源码警告create_model()会执行字段注解中可能包含的任意代码例如字符串引用需要被求值时在接收不可信输入时应谨慎使用。二、核心目标把模型的所有字段变成可选官方示例 docs/examples/dynamic_models.md 的目标非常明确从一个已有模型动态派生一个新模型使其每一个字段都变为可选。典型应用场景包括构建部分更新PATCH请求体、表单可选编辑、测试夹具的灵活构造等。实现要点有三步遍历原模型的model_fields类属性拿到每个字段的FieldInfo实例用FieldInfo.asdict()把字段信息拆成注解 元数据 属性三部分用Annotated重新组装出一个允许None的新注解并通过create_model()传入。2.1model_fields字段信息的入口BaseModel.model_fields是一个类属性字典键是字段名值是对应的FieldInfo实例。它包含了校验、序列化与 JSON Schema 生成所需的全部字段元数据是动态派生模型时读取原始字段定义的标准入口。2.2FieldInfo.asdict()字段信息的可编程表示源码 pydantic/fields.py 对asdict()的定义非常清晰它返回一个包含三个键的字典annotation字段的类型注解metadata类型约束与其他元数据组成的列表如Annotated[int, Field(gt1), WithJsonSchema(...)]中的[Gt(1), WithJsonSchema(...)]attributes剩余字段级属性到值的映射如alias、title等。例如对于如下模型from typing import Annotated from pydantic import BaseModel, Field, WithJsonSchema class Model(BaseModel): f: Annotated[int, Field(gt1), WithJsonSchema({extra: data}), Field(titleF)] 1字段f的FieldInfo.asdict()结果将是annotationintmetadata[Gt(1), WithJsonSchema({extra: data})]类型约束与元数据列表attributes{title: F}剩余字段属性。这样字段的完整配方就被拆解成了可编程操作的三个部分我们可以放心地改造后再重新组装。三、工厂函数实现make_fields_optional()下面是官方示例给出的完整工厂函数。由于 Python 3.10 与 3.11 在Annotated语法上存在差异3.10 使用元组写法3.11 可直接用下标语法示例提供了两个版本。3.1 Python 3.10 版本from typing import Annotated from pydantic import BaseModel, Field, create_model def make_fields_optional(model_cls: type[BaseModel]) - type[BaseModel]: new_fields {} for f_name, f_info in model_cls.model_fields.items(): f_dct f_info.asdict() new_fields[f_name] ( Annotated[(f_dct[annotation] | None, *f_dct[metadata], Field(**f_dct[attributes]))], None, ) return create_model( f{model_cls.__name__}Optional, __base__model_cls, # (1)! **new_fields, )以原模型作为基类可以继承其 校验器、computed fields 等特性我们在new_fields中定义的字段会覆盖父类中的同名字段。3.2 Python 3.11 版本from typing import Annotated from pydantic import BaseModel, Field, create_model def make_fields_optional(model_cls: type[BaseModel]) - type[BaseModel]: new_fields {} for f_name, f_info in model_cls.model_fields.items(): f_dct f_info.asdict() new_fields[f_name] ( Annotated[f_dct[annotation] | None, *f_dct[metadata], Field(**f_dct[attributes])], None, ) return create_model( f{model_cls.__name__}Optional, __base__model_cls, # (1)! **new_fields, )同 3.1 版本以原模型为基类继承校验器、computed fields 等父类字段被我们定义的新字段覆盖。四、逐行拆解新注解是如何重建出来的官方示例给出了新注解的构造模板理解它就能理解整个工厂函数new_annotation Annotated[ f_dct[annotation] | None, # (1)! *f_dct[metadata], # (2)! Field(**f_dct[attributes]), # (3)! ]三个组成部分各有分工f_dct[annotation] | None基于原有注解追加None作为允许值使字段变成可选。在我们的例子中这等价于int | None*f_dct[metadata]解包并复用原有元数据。在我们的例子中等价于在Annotated中显式写出Field(gt1)与WithJsonSchema({extra: data})两个元数据项Field(**f_dct[attributes])通过Field()函数把剩余的字段属性如titleF重新指定回去。随后create_model()的字段定义二元组中第二个元素被设置为None作为新字段的默认值——这正是create_model()字段定义二元组的约定第一个元素是类型第二个元素是默认值或Field()调用见 pydantic/main.py 的参数说明。五、运行演示从必填到全可选让我们用一个更简单的模型来验证工厂函数的效果from typing import Annotated from pydantic import BaseModel, Field class Model(BaseModel): a: Annotated[int, Field(gt1)] ModelOptional make_fields_optional(Model) m ModelOptional() print(m.a) # None可以看到派生出的ModelOptional无需提供任何字段即可实例化字段a的默认值为None且原有的gt1约束依然被保留在新注解中。整个过程完全在运行时完成无需手写新的模型类。六、进阶细节与注意事项6.1 类型标注如何让静态检查更友好工厂函数make_fields_optional()被定义为返回- type[BaseModel]。如果你希望在静态类型检查时尽量保留原始类信息可以使用类型变量Python 3.10from typing import TypeVar ModelTypeT TypeVar(ModelTypeT, boundtype[BaseModel]) def make_fields_optional(model_cls: ModelTypeT) - ModelTypeT: ...Python 3.12PEP 695 语法def make_fields_optional[ModelTypeT: type[BaseModel]](model_cls: ModelTypeT) - ModelTypeT: ...需要提醒的是静态类型检查器无法理解所有字段现在都是可选的这一语义——类型检查器只会看到输入输出是同一个或绑定的模型类不会知道字段的可选性发生了变化。这是动态派生的固有局限在依赖严格类型检查的代码库中需要结合运行时行为来权衡。6.2 用MISSING哨兵替代None作为默认值如果可选但区分未提供与显式 None对业务有意义pydantic 提供了实验性的MISSING哨兵见 docs/concepts/experimental.mdMISSING是单例对象表示验证期间未提供该字段值序列化时值为MISSING的字段会从输出中排除在 JSON Schema 中不会出现MISSING值。在工厂函数中只需把新注解与默认值里的None替换为MISSINGfrom pydantic.experimental.missing_sentinel import MISSING new_fields[f_name] ( Annotated[f_dct[annotation] | MISSING, *f_dct[metadata], Field(**f_dct[attributes])], MISSING, )使用后可以在业务侧通过field is MISSING判断用户是否真的传了这个字段。需要注意MISSING仍属实验特性且包含MISSING值的模型暂不支持 pickle详见 docs/concepts/experimental.md 的相关说明。6.3 为什么不建议直接复制并修改FieldInfo你可能会想直接把原模型的FieldInfo实例copy()一份、加个默认值再作为Annotated元数据复用岂不是更简单官方示例明确给出了警告这种做法虽然在部分情况下能工作但不是受支持的模式随时可能被破坏或废弃。原因在于FieldInfo是内部结构slotted 类且可能被第三方库子类化见 pydantic/fields.py 的_copy()注释直接对其做可变操作超出了公开 API 的保证范围。因此请务必采用本文推荐的asdict()拆解 → 重建注解模式。七、更多应用不止于字段可选化官方文档指出同样的模式可以推广到任何需要从现有模型派生新模型的场景例如移除默认值把字段定义二元组的第二个元素从默认值改为...必填新增别名在Field(**f_dct[attributes])中追加alias...调整约束修改metadata列表中的约束项组合多个基类通过__base__传入元组把多个模型的能力合并进新模型与 docs/concepts/models.md 中的BarModel create_model(BarModel, apple(str, russet), banana(str, yellow), __base__FooModel)用法一致。同时create_model()的__validators__参数可以在动态模型中注入字段校验器见 docs/concepts/models.md 中的UserModel示例进一步扩展了动态建模的表达能力。总结本文基于官方示例 docs/examples/dynamic_models.md完整实现了从已有模型派生全可选字段模型的工厂函数。核心知识链条为create_model()接受字段定义二元组类型 默认值/Field()并支持__base__、__config__、__validators__等定制参数pydantic/main.pymodel_fields暴露每个字段的FieldInfoasdict()将其拆解为annotation/metadata/attributes三部分pydantic/fields.py用Annotated[annotation | None, *metadata, Field(**attributes)]重建可空化注解并把None或MISSING作为默认值回传给create_model()。这套模式既适用于 PATCH 场景的部分更新也可推广到别名调整、默认值修改等任意动态模型派生需求是一份可以直接落地到业务代码中的实战方案。【免费下载链接】pydanticData validation using Python type hints项目地址: https://gitcode.com/GitHub_Trending/py/pydantic创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表