класс данных python3 с ** kwargs (звездочкой) - PullRequest
1 голос
/ 11 марта 2019

В настоящее время я использовал DTO (Data Transfer Object) следующим образом.

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

Но используя dataclass, я не нашел способа реализовать его.

Есть ли здесь какое-нибудь решение?

Спасибо.


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

Результат: {'user_id': 'hide', 'body': 'body test'}

Как вы знаете, я хочу вставить данные с типом словаря -> **temp

Причина использования звездочки в классе данных та же.

Я должен передать диктативный тип классу init.

Есть идеи?

1 Ответ

3 голосов
/ 11 марта 2019

Основной вариант использования для классов данных - предоставить контейнер, который сопоставляет аргументы с атрибутами. Если у вас есть неизвестные аргументы, вы не можете знать соответствующие атрибуты при создании класса.

Вы можете обойти это, если во время инициализации знаете, какие аргументы неизвестны, отправив их вручную в атрибут catch-all:

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__ (что в значительной степени противоречит цели использования dataclasses в первое место) пишет @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'
...