【问题标题】:Decorator to Nested List of Dataclass in PythonPython中数据类嵌套列表的装饰器
【发布时间】:2019-11-26 13:57:51
【问题描述】:

我想为一个数据类创建一个字典,其中包含作为属性的数据类列表

这是我想要实现的示例:

from typing import List
from dataclasses import dataclass


@dataclass
class level2:
    key21: int
    key22: int


@nested_dataclass
class level1:
    key1: int
    key2: List[level2]


data = {
    'key1': value1,
    'key2': [{
        'key21': value21,
        'key22': value22,
    }]
}

my_object = level1(**data)
print(my_object.key2[0].key21) #should print value21

我发现最近的装饰器是这个,但它不适用于数据类列表: Creating nested dataclass objects in Python

def is_dataclass(obj):
    """Returns True if obj is a dataclass or an instance of a
    dataclass."""
    _FIELDS = '__dataclass_fields__'
    return hasattr(obj, _FIELDS)


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

您将如何修改此装饰器或创建一个可以完成这项工作的装饰器? (我在建筑装饰师方面的经验为零)

非常感谢任何评论/代码。谢谢

【问题讨论】:

  • key2 应该是一个列表吧?我不认为你的data 声明是正确的
  • 对,正在修改帖子,谢谢指出

标签: python list nested decorator python-dataclasses


【解决方案1】:

好的,所以我稍微更改了装饰器,但它对此处提供的示例非常具体。主要问题是您的List[level2] 字段不是dataclass。所以为了解决这个问题,我玩了一下,发现有一个 args 属性可以告诉你列表中的嵌套类型。我以前从未使用过数据类(pydantic 除外),所以也许有更好的答案

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 hasattr(field_type, '__args__'):
                    inner_type = field_type.__args__[0]
                    if is_dataclass(inner_type):
                        new_obj = [inner_type(**dict_) for dict_ in value]
                        kwargs[name] = new_obj

            original_init(self, *args, **kwargs)

        cls.__init__ = __init__
        return cls

    return wrapper(args[0]) if args else wrapper


@dataclass
class level2:
    key21: int
    key22: int

@nested_dataclass
class level1:
    key1: int
    key2: List[level2]


data = {
    'key1': 1,
    'key2': [{
        'key21': 21,
        'key22': 22,
    },
    {
     'key21': 23,
     'key22': 24
     }]
}

my_object = level1(**data)
print(my_object.key2[0].key21) #should print 21
print(my_object.key2[1].key21) #should print 23

@nested_dataclass
class random:
    key1: int
    key2: List[int]

random_object = random(**{'key1': 1, 'key2': [1,2,3]})
print(random_object.key2) # prints [1,2,3]

进一步嵌套

@nested_dataclass
class level3:
    key3: List[level1]

level3(**{'key3': [data]})

输出:

level3(key3=[level1(key1=1, key2=[level2(key21=21, key22=22), level2(key21=23, key22=24)])])

【讨论】:

  • 刚刚发现一个不行的情况:dataclass class level3: key31: int nested_dataclass class level2: key21: level3 nested_dataclass class level1: key11: List[level2] data = { 'key11': [ { 'key21': { 'key31': 21, } }] } my_object = level1(**data) print(my_object) 但是,如果 nested_dataclass class level2: key21: List[level3] 它可以工作。我真的不明白为什么。有什么想法吗?
  • 我提供的解决方案非常适合上述案例。您的新level2 不像上面的情况那样嵌套,其中嵌套仅用于其他数据类的List。您可以修改我必须为您工作的代码,但要概括它,您将需要更多的代码。所以我再次建议使用pydantic
  • 感谢@Buckeye14Guy,将尝试使其工作并发布通用解决方案
【解决方案2】:

pip install 验证-dc

验证DC:https://github.com/EvgeniyBurdin/validated_dc

from dataclasses import dataclass
from typing import List, Union

from validated_dc import ValidatedDC


@dataclass
class Level2(ValidatedDC):
    key21: int
    key22: int


@dataclass
class Level1(ValidatedDC):
    key1: int
    key2: List[Level2]


data = {
    'key1': 1,
    'key2': [{
        'key21': 21,
        'key22': 22,
    }]
}

my_object = Level1(**data)
assert my_object.key2[0].key21 == 21


# ----------------------------------

@dataclass
class Level1(ValidatedDC):
    key1: int
    key2: Union[Level2, List[Level2]]


my_object = Level1(**data)  # key2 - list
assert my_object.key2[0].key21 == 21

data['key2'] = {
    'key21': 21,
    'key22': 22,
}

my_object = Level1(**data)  # key2 - dict
assert my_object.key2.key21 == 21

【讨论】:

    【解决方案3】:

    这不提供如何更改装饰器,如果您不想使用任何 3rd 方包,请忽略此答案。但我认为 pydantic 可以做你想做的事。我建议的唯一原因是因为它不允许您在将 key2 声明为列表时错误地将其作为字典。

    from typing import List
    from pydantic import BaseModel
    class level2(BaseModel):
        key21: int
        key22: int
    
    class level1(BaseModel):
        key1: int
        key2: List[level2]
    
    data = {
        'key1': 1,
        'key2': [{
            'key21': 21,
            'key22': 22,
        }]
    }
    
    my_object = level1(**data)
    print(my_object.key2[0].key21) # prints 21
    

    如果你真的想直接从key2 访问key21 那么

    class level1(BaseModel):
        key1: int
        key2: level2 # Not a list
    
    data = {
        'key1': 1,
        'key2': {
            'key21': 21,
            'key22': 22,
        }
    }
    
    my_object = level1(**data)
    print(my_object.key2.key21) # prints 21
    

    如果您的目标是成功让装饰器工作,请再次忽略这一点。否则安装 pydantic 不会受到伤害:)

    【讨论】:

    • 主要问题是List[level2] 不是data_class,所以if 语句没有被使用。
    猜你喜欢
    • 2015-06-11
    • 2020-01-15
    • 2013-03-12
    • 2021-12-05
    • 2019-08-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-04
    • 1970-01-01
    相关资源
    最近更新 更多