在 Python 中创建嵌套数据类对象

新手上路,请多包涵

我有一个数据类对象,其中嵌套了数据类对象。但是,当我创建主对象时,嵌套对象变成了字典:

 @dataclass
class One:
    f_one: int
    f_two: str

@dataclass
class Two:
    f_three: str
    f_four: One

Two(**{'f_three': 'three', 'f_four': {'f_one': 1, 'f_two': 'two'}})

Two(f_three='three', f_four={'f_one': 1, 'f_two': 'two'})

obj = {'f_three': 'three', 'f_four': One(**{'f_one': 1, 'f_two': 'two'})}

Two(**obj)
Two(f_three='three', f_four=One(f_one=1, f_two='two'))

如您所见,只有 **obj 有效。

理想情况下,我想构建我的对象以获得如下内容:

 Two(f_three='three', f_four=One(f_one=1, f_two='two'))

除了在访问对象属性时手动将嵌套字典转换为相应的数据类对象之外,还有什么方法可以实现吗?

原文由 mohi666 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1k
2 个回答

这是一个与 dataclasses 模块本身一样复杂的请求,这意味着实现这种“嵌套字段”功能的最佳方法可能是定义一个新的装饰器,类似于 @dataclass

幸运的是,如果您不需要 __init__ 方法的签名来反映字段及其默认值,就像调用 dataclass 呈现的类一样,这可以简单得多:一个将调用原始 dataclass 并在其生成的 __init__ 方法上包装一些功能的类装饰器可以用一个普通的“ ...(*args, **kwargs): ”函数来完成它。—

换句话说,所有需要做的就是围绕生成的 __init__ 方法编写一个包装器,它将检查传入“kwargs”的参数,检查是否有对应于“数据类字段类型”,如果是,在调用原始 __init__ 之前生成嵌套对象。也许这在英语中比在 Python 中更难拼写:

 from dataclasses import dataclass, is_dataclass

def nested_dataclass(*args, **kwargs):
    def wrapper(cls):
        cls = dataclass(cls, **kwargs)
        original_init = cls.__init__
        def __init__(self, *args, **kwargs):
            for name, value in kwargs.items():
                field_type = cls.__annotations__.get(name, None)
                if is_dataclass(field_type) and isinstance(value, dict):
                     new_obj = field_type(**value)
                     kwargs[name] = new_obj
            original_init(self, *args, **kwargs)
        cls.__init__ = __init__
        return cls
    return wrapper(args[0]) if args else wrapper

请注意,除了不用担心 __init__ 签名外,这也忽略了传递 init=False - 因为无论如何它都是没有意义的。

(返回行中的 if 负责使用命名参数调用或直接作为装饰器调用,例如 dataclass 本身)

在交互式提示上:

 In [85]: @dataclass
    ...: class A:
    ...:     b: int = 0
    ...:     c: str = ""
    ...:

In [86]: @dataclass
    ...: class A:
    ...:     one: int = 0
    ...:     two: str = ""
    ...:
    ...:

In [87]: @nested_dataclass
    ...: class B:
    ...:     three: A
    ...:     four: str
    ...:

In [88]: @nested_dataclass
    ...: class C:
    ...:     five: B
    ...:     six: str
    ...:
    ...:

In [89]: obj = C(five={"three":{"one": 23, "two":"narf"}, "four": "zort"}, six="fnord")

In [90]: obj.five.three.two
Out[90]: 'narf'

如果您希望保留签名,我建议使用 dataclasses 模块本身中的私有辅助函数来创建一个新的 __init__

原文由 jsbueno 发布,翻译遵循 CC BY-SA 4.0 许可协议

您可以为此使用 post_init

 from dataclasses import dataclass
@dataclass
class One:
    f_one: int
    f_two: str

@dataclass
class Two:
    f_three: str
    f_four: One
    def __post_init__(self):
        self.f_four = One(**self.f_four)

data = {'f_three': 'three', 'f_four': {'f_one': 1, 'f_two': 'two'}}

print(Two(**data))
# Two(f_three='three', f_four=One(f_one=1, f_two='two'))

原文由 Peter Mølgaard Pallesen 发布,翻译遵循 CC BY-SA 4.0 许可协议

推荐问题