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

查看:31
本文介绍了在 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 is as complex as 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):"样式函数.

Fortunately, if you don'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"中传递的参数,检查是否有任何对应于数据类"字段类型",如果是,则在调用原始 __init__ 之前生成嵌套对象.也许这用英语比 Python 更难拼写:

In other words, all one needs to do is write a wrapper around 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天全站免登陆