首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >在Python中创建嵌套的数据类对象

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

Stack Overflow用户
提问于 2018-07-28 04:03:48
回答 7查看 23K关注 0票数 28

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

代码语言:javascript
复制
@dataclass
class One:
    f_one: int

@dataclass
class One:
    f_one: int
    f_two: str

@dataclass
class Two:
    f_three: str
    f_four: One


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

two = Two(**data)

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_2 = Two(**data)

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

正如您所看到的,我尝试将所有数据作为字典进行传递,但没有得到预期的结果。然后,我尝试首先构造嵌套对象,并通过对象构造函数传递它,但我得到了相同的结果。

理想情况下,我希望构造我的对象来获得类似下面这样的东西:

代码语言:javascript
复制
Two(f_three='three', f_four=One(f_one=1, f_two='two'))

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

提前谢谢。

EN

回答 7

Stack Overflow用户

回答已采纳

发布于 2018-07-28 05:39:50

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

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

换句话说,所有人需要做的就是在生成的__init__方法周围编写一个包装器,该包装器将检查“__init__”中传递的参数,检查是否有对应于"dataclass type“的参数,如果有,则在调用原始kwargs之前生成嵌套对象。也许这在英语中比在Python中更难理解:

代码语言:javascript
复制
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本身)调用)

在交互式提示符下:

代码语言:javascript
复制
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__

票数 30
EN

Stack Overflow用户

发布于 2018-10-15 20:24:22

你可以试试dacite模块。这个包简化了从字典创建数据类的过程--它还支持嵌套结构。

示例:

代码语言:javascript
复制
from dataclasses import dataclass
from dacite import from_dict

@dataclass
class A:
    x: str
    y: int

@dataclass
class B:
    a: A

data = {
    'a': {
        'x': 'test',
        'y': 1,
    }
}

result = from_dict(data_class=B, data=data)

assert result == B(a=A(x='test', y=1))

要安装dacite,只需使用pip:

代码语言:javascript
复制
$ pip install dacite
票数 15
EN

Stack Overflow用户

发布于 2018-09-03 18:56:53

我没有编写新的装饰器,而是想出了一个函数,在初始化实际的dataclass之后修改所有dataclass类型的字段。

代码语言:javascript
复制
def dicts_to_dataclasses(instance):
    """Convert all fields of type `dataclass` into an instance of the
    specified data class if the current value is of type dict."""
    cls = type(instance)
    for f in dataclasses.fields(cls):
        if not dataclasses.is_dataclass(f.type):
            continue

        value = getattr(instance, f.name)
        if not isinstance(value, dict):
            continue

        new_value = f.type(**value)
        setattr(instance, f.name, new_value)

该函数可以手动调用,也可以在__post_init__中调用。这样,@dataclass装饰器就可以大显身手了。

上面的示例调用了__post_init__

代码语言:javascript
复制
@dataclass
class One:
    f_one: int
    f_two: str

@dataclass
class Two:
    def __post_init__(self):
        dicts_to_dataclasses(self)

    f_three: str
    f_four: One

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

two = Two(**data)
# Two(f_three='three', f_four=One(f_one=1, f_two='two'))
票数 11
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/51564841

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档