pydantic 作为一款强大的数据验证和序列化库,在现代python应用开发中扮演着核心角色。然而,在处理某些特定场景时,我们可能会遇到一个常见的需求:当模型配置允许额外字段(configdict(extra="allow"))时,model_dump() 方法会将这些未在模型中明确定义的额外数据也包含在序列化输出中。尽管 model_dump 提供了 exclude 等参数用于排除特定字段,但并没有直接的 exclude_extras 选项来一劳永逸地排除所有额外字段,尤其是在面对复杂的多层嵌套模型时,手动迭代和清理输出会变得异常繁琐且容易出错。
例如,考虑以下Pydantic模型定义:
from pydantic import BaseModel, ConfigDict class Nested(BaseModel): model_config = ConfigDict(extra="allow") baz: str class Root(BaseModel): foo: int = 10 bar: int nested: Nested # 当创建模型实例时,nested 字典中包含了一个未定义的 "extra" 字段 model = Root(foo=10, bar=20, nested={"baz": "boing", "extra": "so special"}) # 默认的 model_dump 会包含这个 "extra" 字段 dumped_data = model.model_dump() # dumped_data['nested'] 会是 {'baz': 'boing', 'extra': 'so special'} # 我们的目标是让 assert "extra" not in dumped_data["nested"] 成立
在这种情况下,我们期望 model_dump() 的结果中不包含 nested 字典中的 "extra" 字段。为了实现这一目标,我们可以利用 Pydantic V2 提供的 model_serializer 装饰器,结合 wrap 模式,创建一个自定义的基类来统一处理所有模型的序列化行为。
解决方案:定制化 MyBaseModel 排除额外字段核心思想是创建一个名为 MyBaseModel 的基类,所有需要此功能的 Pydantic 模型都将继承自它。在这个基类中,我们定义一个包装序列化器,它会在 Pydantic 完成默认的序列化后,对结果字典进行过滤,只保留那些在模型中明确定义的字段。

全面的AI聚合平台,一站式访问所有顶级AI模型


from typing import Any from pydantic import BaseModel, ConfigDict, model_serializer, SerializerFunctionWrapHandler, FieldSerializationInfo class MyBaseModel(BaseModel): """ 一个自定义的Pydantic基类,用于在序列化时自动排除未定义的额外字段。 """ @model_serializer(mode="wrap") def _serialize(self, handler: SerializerFunctionWrapHandler) -> dict[str, Any]: """ 包装序列化器,在默认序列化结果的基础上过滤掉额外字段。 :param handler: Pydantic提供的默认序列化处理器。 :return: 经过过滤后的字典表示。 """ # 调用handler(self)获取Pydantic默认的序列化结果 default_dumped_data = handler(self) # 使用字典推导式过滤结果,只保留模型中实际定义的字段 # self.model_fields 包含了所有明确定义的字段名称 filtered_data = { k: v for k, v in default_dumped_data.items() if k in self.model_fields } return filtered_data class Nested(MyBaseModel): """ 一个嵌套模型,允许额外字段,但序列化时会排除它们。 """ model_config = ConfigDict(extra="allow") baz: str class Root(MyBaseModel): """ 根模型,包含嵌套模型,序列化时会排除所有层级的额外字段。 """ foo: int = 10 bar: int nested: Nested if __name__ == "__main__": # 创建包含额外字段的模型实例 model = Root(foo=10, bar=20, nested={"baz": "boing", "extra": "so special"}) # 调用 model_dump 进行序列化 dumped_data = model.model_dump() print(f"原始模型实例: {model}") print(f"序列化后的数据: {dumped_data}") # 验证 "extra" 字段是否已被排除 assert "extra" not in dumped_data["nested"] print("断言成功:'extra' 字段已从嵌套模型中排除。") # 验证其他字段是否正常保留 assert dumped_data["foo"] == 10 assert dumped_data["bar"] == 20 assert dumped_data["nested"]["baz"] == "boing" print("断言成功:定义的字段正常保留。")代码解析
- MyBaseModel(BaseModel): 我们创建了一个新的基类 MyBaseModel,它继承自 Pydantic 的 BaseModel。
-
@model_serializer(mode="wrap"): 这是 Pydantic V2 引入的一个强大特性。
- model_serializer 装饰器允许我们为模型定义自定义的序列化逻辑。
- mode="wrap" 指定了序列化器的工作模式为“包装”。这意味着我们的 _serialize 方法将接收一个 handler 函数作为参数。这个 handler 函数会执行 Pydantic 默认的序列化逻辑,并返回一个字典。我们的包装器可以在此基础上对结果进行修改。
-
_serialize(self, handler: SerializerFunctionWrapHandler) -> dict[str, Any]:
- self: 当前模型实例。
- handler: 一个可调用对象,调用 handler(self) 会返回模型实例的默认序列化字典表示。
- default_dumped_data = handler(self): 这一步是关键。它首先让 Pydantic 完成其常规的序列化过程,生成一个包含所有字段(包括额外字段,如果 extra="allow")的字典。
-
filtered_data = {k: v for k, v in default_dumped_data.items() if k in self.model_fields}:
- self.model_fields: 这是 Pydantic 模型的一个内部属性,它是一个字典,包含了模型中所有明确定义字段的名称(作为键)和它们的 FieldInfo 对象(作为值)。通过检查 k in self.model_fields,我们可以确定一个键是否是模型中定义的合法字段。
- 这个字典推导式遍历 default_dumped_data 中的所有键值对,只保留那些键存在于 self.model_fields 中的项。这样,所有未在模型中定义的额外字段都会被过滤掉。
- return filtered_data: 返回经过过滤后的字典,作为 model_dump() 的最终结果。
- 继承关系: 确保所有需要排除额外字段的模型都直接或间接继承自 MyBaseModel。
- Pydantic V2 特性: model_serializer 是 Pydantic V2 的特性。如果您使用的是 Pydantic V1,需要采用不同的方法(例如重写 json() 或 dict() 方法,但实现会更复杂)。
- 性能考量: 这种方法在默认序列化之后增加了一次字典遍历和过滤操作。对于大多数应用而言,这种开销可以忽略不计。但在处理极其庞大或序列化频率极高的模型时,需要评估其对性能的潜在影响。
- 通用性: 这种方法是通用的,它会作用于所有继承 MyBaseModel 的模型实例,无论它们是根模型还是嵌套模型,都能确保其 model_dump 输出不包含额外字段。
- 与 exclude 参数的区别: model_dump 的 exclude 参数用于排除模型中 已定义 的特定字段。而我们这里的解决方案是排除模型中 未定义 的所有额外字段,两者目的不同,可以结合使用。
通过这种定制化的 MyBaseModel 方案,我们为 Pydantic 模型提供了一种优雅、可复用且强大的机制,以确保在序列化输出中严格控制数据内容,只包含模型架构中明确定义的字段,从而提升数据输出的清晰度和一致性。
以上就是Pydantic model_dump 忽略 extra 字段的优雅实现的详细内容,更多请关注知识资源分享宝库其它相关文章!
相关标签: python js json 处理器 ai 应用开发 区别 键值对 red Python 架构 json if for 继承 对象 应用开发 大家都在看: Python怎么获取CPU核心数_os与multiprocessing获取CPU核心数 python人马兽系列 python人马兽系列的主要内容 Python怎么创建虚拟环境_Python虚拟环境创建与管理教程 python如何计算列表的长度_python使用len()函数获取列表长度 python怎么判断一个变量的类型_python变量类型判断方法
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。