如何定义含多子模型混合列表字段的Pydantic模型?
问题描述
我正在使用Pydantic定义一个大型模型,在定义包含多个子模型的列表字段时遇到了问题,以下是示例代码:
from pydantic import BaseModel from typing import Literal, Union class Model_A(BaseModel): type: Literal["A"] = "A" name: str size: float class Model_B(BaseModel): type: Literal["B"] = "B" name: str size: float color: str value: float class GeneralModel(BaseModel): name: str objects_list: list[Union[Model_A, Model_B]] if __name__ == "__main__": user_list = { "name": "John Doe", "objects_list": [ {"name": "box 1", "size": 3}, {"name": "box 2", "size": 4, "color": "red", "value": 5}, {"name": "box 3", "size": 6}, ], } # 我希望构造对应类型的实例,但当前代码仅生成Union中第一个定义的类型 order = GeneralModel(**user_list) assert isinstance(order.objects_list[0], Model_A) assert isinstance(order.objects_list[1], Model_B) # 失败,被构造成Model_A实例
请问是否有办法解决该问题,让Pydantic自动选择正确的模型类型?
解决方案
Pydantic默认会按Union中模型的顺序尝试匹配,只要第一个模型能通过验证(比如Model_A默认忽略额外字段,导致带color/value的对象也能匹配),就不会尝试后续模型。要实现自动匹配,需要用鉴别联合(Discriminated Unions),结合type字段区分模型,同时调整验证规则避免误匹配。
方法一:Pydantic v2 推荐实现
使用Annotated搭配discriminated_union指定鉴别字段,同时设置extra="forbid"防止额外字段导致的误匹配:
from pydantic import BaseModel, discriminated_union from typing import Literal, Annotated, Union class Model_A(BaseModel): type: Literal["A"] = "A" name: str size: float model_config = {"extra": "forbid"} # 禁止额外字段,避免误匹配 class Model_B(BaseModel): type: Literal["B"] = "B" name: str size: float color: str value: float model_config = {"extra": "forbid"} class GeneralModel(BaseModel): name: str # 基于type字段自动选择对应模型 objects_list: list[Annotated[Union[Model_A, Model_B], discriminated_union("type")]] if __name__ == "__main__": user_list = { "name": "John Doe", "objects_list": [ {"name": "box 1", "size": 3}, {"name": "box 2", "size": 4, "color": "red", "value": 5}, {"name": "box 3", "size": 6}, ], } order = GeneralModel(**user_list) assert isinstance(order.objects_list[0], Model_A) assert isinstance(order.objects_list[1], Model_B) # 断言成功
方法二:Pydantic v1 兼容实现
在v1中开启smart_union=True,让Pydantic尝试所有模型找到最匹配的,同时设置extra="forbid":
from pydantic import BaseModel from typing import Literal, Union class Model_A(BaseModel): type: Literal["A"] = "A" name: str size: float class Config: smart_union = True extra = "forbid" class Model_B(BaseModel): type: Literal["B"] = "B" name: str size: float color: str value: float class Config: smart_union = True extra = "forbid" class GeneralModel(BaseModel): name: str objects_list: list[Union[Model_A, Model_B]] class Config: smart_union = True if __name__ == "__main__": user_list = { "name": "John Doe", "objects_list": [ {"name": "box 1", "size": 3}, {"name": "box 2", "size": 4, "color": "red", "value": 5}, {"name": "box 3", "size": 6}, ], } order = GeneralModel(**user_list) assert isinstance(order.objects_list[0], Model_A) assert isinstance(order.objects_list[1], Model_B) # 断言成功
优化建议
如果允许修改输入数据,显式传入type字段能让匹配更准确,无需依赖extra限制:
user_list = { "name": "John Doe", "objects_list": [ {"type": "A", "name": "box 1", "size": 3}, {"type": "B", "name": "box 2", "size": 4, "color": "red", "value": 5}, {"type": "A", "name": "box 3", "size": 6}, ], }
内容的提问来源于stack exchange,提问作者Grmn
相关产品推荐
相关产品推荐

