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)
正如评论中正确指出的那样,如果不存储附加信息,则在解析时无法区分模型。
截至今天(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)