区分具有相同字段的 Pydantic 模型

twh*_*hes 11 python abc pydantic

我使用 Pydantic 定义分层数据,其中存在具有相同属性的模型。

但是,当我保存和加载这些模型时,Pydantic 无法再区分使用了哪个模型,并选择字段类型注释中的第一个模型。

我知道这是基于文档的预期行为。但是,类类型信息对我的应用程序很重要。

在 Pydantic 中区分不同类的推荐方法是什么?一种技巧是简单地向其中一个模型添加一个无关的字段,但我想找到一种更优雅的解决方案。

请参阅下面的简化示例:container使用 类型的数据进行初始化DataB,但在导出和加载之后,newcontainer具有类型的数据DataA,因为它是 类型声明中的第一个元素container.data

感谢您的帮助!

from abc import ABC
from pydantic import BaseModel #pydantic 1.8.2
from typing import Union

class Data(BaseModel, ABC):
    """ base class for a Member """
    number: float

class DataA(Data):
    """ A type of Data"""
    pass

class DataB(Data):
    """ Another type of Data """
    pass

class Container(BaseModel):
    """ container holds a subclass of Data """
    data: Union[DataA, DataB]

# initialize container with DataB
data = DataB(number=1.0)
container = Container(data=data)

# export container to string and load new container from string
string = container.json()
new_container = Container.parse_raw(string)

# look at type of container.data
print(type(new_container.data).__name__)
# >>> DataA
Run Code Online (Sandbox Code Playgroud)

ale*_*ame 6

正如评论中正确指出的那样,如果不存储附加信息,则在解析时无法区分模型。

截至今天(pydantic v1.8.2),在 a 中解析时Union(以防歧义)区分模型的最规范方法是显式添加类型说明符Literal。它看起来像这样:

from abc import ABC
from pydantic import BaseModel
from typing import Union, Literal

class Data(BaseModel, ABC):
    """ base class for a Member """
    number: float


class DataA(Data):
    """ A type of Data"""
    tag: Literal['A'] = 'A'


class DataB(Data):
    """ Another type of Data """
    tag: Literal['B'] = 'B'


class Container(BaseModel):
    """ container holds a subclass of Data """
    data: Union[DataA, DataB]


# initialize container with DataB
data = DataB(number=1.0)
container = Container(data=data)

# export container to string and load new container from string
string = container.json()
new_container = Container.parse_raw(string)


# look at type of container.data
print(type(new_container.data).__name__)
# >>> DataB
Run Code Online (Sandbox Code Playgroud)

此方法可以自动化,但您可以自行负责使用它,因为它会破坏静态类型并使用可能在未来版本中更改的对象:

from pydantic.fields import ModelField

class Data(BaseModel, ABC):
    """ base class for a Member """
    number: float

    def __init_subclass__(cls, **kwargs):
        name = 'tag'
        value = cls.__name__
        annotation = Literal[value]

        tag_field = ModelField.infer(name=name, value=value, annotation=annotation, class_validators=None, config=cls.__config__)
        cls.__fields__[name] = tag_field
        cls.__annotations__[name] = annotation


class DataA(Data):
    """ A type of Data"""
    pass


class DataB(Data):
    """ Another type of Data """
    pass
Run Code Online (Sandbox Code Playgroud)