带有** kwargs(星号)的python3数据类

时间:2019-03-11 09:56:01

标签: python python-3.7 python-dataclasses

目前,我像这样使用DTO(数据传输对象)。

class Test1:
    def __init__(self, 
        user_id: int = None,
        body: str = None):
        self.user_id = user_id
        self.body = body

示例代码很小,但是当对象规模增长时,我必须定义每个变量。

在深入研究时发现python 3.7支持dataclass

下面的代码是DTO使用的数据类。

from dataclasses import dataclass


@dataclass
class Test2:
    user_id: int
    body: str

在这种情况下,如何允许在class Test2中传递更多未定义的参数?

如果我使用Test1,这很容易。只需将**kwargs(asterisk)添加到__init__

class Test1:
    def __init__(self, 
        user_id: int = None,
        body: str = None,
        **kwargs):
        self.user_id = user_id
        self.body = body

但是使用数据类,找不到实现它的任何方法。

这里有什么解决办法吗?

谢谢。


编辑

class Test1:
    def __init__(self,
        user_id: str = None, 
        body: str = None):
        self.user_id = user_id
        self.body = body

if __name__ == '__main__':
    temp = {'user_id': 'hide', 'body': 'body test'}
    t1 = Test1(**temp)
    print(t1.__dict__)

结果:{'user_id': 'hide', 'body': 'body test'}

如您所知,我要插入字典类型为-> **temp

的数据

在数据类中使用星号的原因相同。

我必须将字典类型传递给init类。

这里有什么主意吗?

3 个答案:

答案 0 :(得分:4)

数据类仅依赖于__init__方法,因此您可以在__new__方法中随意更改类。

from dataclasses import dataclass


@dataclass
class Container:
    user_id: int
    body: str

    def __new__(cls, *args, **kwargs):
        try:
            initializer = cls.__initializer
        except AttributeError:
            # Store the original init on the class in a different place
            cls.__initializer = initializer = cls.__init__
            # replace init with something harmless
            cls.__init__ = lambda *a, **k: None

        # code from adapted from Arne
        added_args = {}
        for name in list(kwargs.keys()):
            if name not in cls.__annotations__:
                added_args[name] = kwargs.pop(name)

        ret = object.__new__(cls)
        initializer(ret, **kwargs)
        # ... and add the new ones by hand
        for new_name, new_val in added_args.items():
            setattr(ret, new_name, new_val)

        return ret


if __name__ == "__main__":
    params = {'user_id': 1, 'body': 'foo', 'bar': 'baz', 'amount': 10}
    c = Container(**params)
    print(c.bar)  # prints: 'baz'
    print(c.body)  # prints: 'baz'`

答案 1 :(得分:3)

数据类的基本用例是提供一个将参数映射到属性的容器。如果您有未知的参数,则在类创建过程中将不知道各自的属性。

如果您在初始化期间知道哪些参数未知,可以手动解决该问题,方法是手动将它们发送到一个包罗万象的属性:

from dataclasses import dataclass, field

@dataclass
class Container:
    user_id: int
    body: str
    meta: field(default_factory=dict)

# usage:
obligatory_args = {'user_id': 1, 'body': 'foo'}
other_args = {'bar': 'baz', 'amount': 10}
c = Container(**obligatory_args, meta=other_args)
print(c.meta['bar'])  # prints: 'baz'

但是在这种情况下,您仍然需要查看字典,并且无法按名称访问参数,即c.bar不起作用。


如果您关心按名称访问属性,或者在初始化期间无法区分已知参数和未知参数,那么您的最后一招就是不重写__init__(这几乎违背了使用{{1 }}首先是写一个dataclasses

@classmethod

答案 2 :(得分:2)

这是我使用过的一种简洁的变体。

from dataclasses import dataclass, field
from typing import Optional, Dict


@dataclass
class MyDataclass:
    data1: Optional[str] = None
    data2: Optional[Dict] = None
    data3: Optional[Dict] = None

    kwargs: field(default_factory=dict) = None

    def __post_init__(self):
        [setattr(self, k, v) for k, v in self.kwargs.items()]

其工作原理如下:

>>> data = MyDataclass(data1="data1", kwargs={"test": 1, "test2": 2})
>>> data.test
1
>>> data.test2
2

但是请注意,数据类似乎不知道它具有以下新属性:

>>> from dataclasses import asdict
>>> asdict(data)
{'data1': 'data1', 'data2': None, 'data3': None, 'kwargs': {'test': 1, 'test2': 2}}

这意味着必须知道密钥。这适用于我的用例,也可能适用于其他用例。