You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何定义含多子模型混合列表字段的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 10:08:12