python3 dataclass with **kwargs(asterisk)

断了今生、忘了曾经 提交于 2019-12-11 05:13:34

问题


Currently I used DTO(Data Transfer Object) like this.

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

Example code is very small, But when object scale growing up, I have to define every variable.

While digging into it, found that python 3.7 supported dataclass

Below code is DTO used dataclass.

from dataclasses import dataclass


@dataclass
class Test2:
    user_id: int
    body: str

In this case, How can I allow pass more argument that does not define into class Test2?

If I used Test1, it is easy. Just add **kwargs(asterisk) into __init__

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

But using dataclass, Can't found any way to implement it.

Is there any solution here?

Thanks.


EDIT

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

Result : {'user_id': 'hide', 'body': 'body test'}

As you know, I want to insert data with dictionary type -> **temp

Reason to using asterisk in dataclass is the same.

I have to pass dictinary type to class init.

Any idea here?


回答1:


The basic use case for dataclasses is to provide a container that maps arguments to attributes. If you have unknown arguments, you can't know the respective attributes during class creation.

You can work around it if you know during initialization which arguments are unknown by sending them to a catch-all attribute by hand:

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'

But in this case you'll still have a dictionary you need to look into and can't access the arguments by their name, i.e. c.bar doesn't work.


If you care about accessing attributes by name, or if you can't distinguish between known and unknown arguments during initialisation, then your last resort without rewriting __init__ (which pretty much defeats the purpose of using dataclasses in the first place) is writing a @classmethod:

@dataclass
class Container:
    user_id: int
    body: str

    @classmethod
    def from_kwargs(cls, **kwargs):

        # split the kwargs into native ones and new ones
        native_args, new_args = {}, {}
        for name, val in kwargs.items():
            if name in cls.__annotations__:
                native_args[name] = val
            else:
                new_args[name] = val

        # use the native ones to create the class ...
        ret = cls(**native_args)

        # ... and add the new ones by hand
        for new_name, new_val in new_args.items():
            setattr(ret, new_name, new_val)
        return ret

# usage:
params = {'user_id': 1, 'body': 'foo', 'bar': 'baz', 'amount': 10}
Container(**params)  # still doesn't work, raises a TypeError 
c = Container.from_kwargs(**params)
print(c.bar)  # prints: 'baz'


来源:https://stackoverflow.com/questions/55099243/python3-dataclass-with-kwargsasterisk

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!