在Python中创建嵌套的数据类对象 [英] Creating nested dataclass objects in Python

查看:439
本文介绍了在Python中创建嵌套的数据类对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a dataclass object that has nested dataclass objects in it. However, when I create the main object, the nested objects turn into a dictionary:

@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'})

如您所见,我试图将所有数据作为字典传递,但没有得到预期的结果.然后,我尝试先构造嵌套对象,然后将其传递给对象构造函数,但得到的结果相同.

As you can see I tried to pass all the data as a dictionary, but I didn't get the intended result. Then I tried to construct the nested object first and pass it through the object constructor, but I got the same result.

理想情况下,我想构造我的对象以获得类似这样的内容:

Ideally I'd like to construct my object to get something like this:

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

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

Is there any way to achieve that other than manually converting nested dictionaries to corresponding dataclass object, whenever accessing object attributes?

谢谢.

推荐答案

此请求的复杂度与dataclasses模块本身的复杂度匹配:这意味着可能是实现此嵌套字段"功能的最佳方法是要定义一个类似于@dataclass的新装饰器.

This is a request that have complexity matching the complexity of the dataclasses module itself: which means that probably the best way to achieve this "nested fields" capability is to define a new decorator, akin to @dataclass.

幸运的是,如果不需要__init__方法的签名来反映字段及其默认值,例如通过调用dataclass呈现的类,则可以简单得多:类装饰器将调用原始的dataclass并在其生成的__init__方法上包装一些功能可以使用普通的"...(*args, **kwargs):"样式函数来实现.

Fortunatelly, if one won't need the signature of the __init__ method to reflect the fields and their defaults, like the classes rendered by calling dataclass, this can be a whole lot simpler: A class decorator that will call the original dataclass and wrap some functionality over its generated __init__ method can do it with a plain "...(*args, **kwargs):" style function.

换句话说,所有要做的就是对生成的__init__方法进行包装,该方法将检查在"kwargs"中传递的参数,检查是否与"dataclass字段类型"相对应,如果是,则生成调用原始__init__之前的嵌套对象.也许用英语比用Python拼写更难:

In other words, all one needs to do is a wrapper over the generated __init__ method that will inspect the parameters passed in "kwargs", check if any corresponds to a "dataclass field type", and if so, generate the nested object prior to calling the original __init__. Maybe this is harder to spell out in English than in 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的原因-因为它还是毫无意义的.

Note that besides not worrying about __init__ signature, this also ignores passing init=False - since it would be meaningless anyway.

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

(The if in the return line is responsible for this to work either being called with named parameters or directly as a decorator, like dataclass itself)

在交互式提示上:

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__.

If you want the signature to be kept, I'd recommend using the private helper functions in the dataclasses module itself, to create a new __init__.

这篇关于在Python中创建嵌套的数据类对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆