Как правильно в Python определить класс данных, который имеет автоматически сгенерированный __init__ и дополнительный init2 из набора значений - PullRequest
2 голосов
/ 02 июля 2019

В Python у меня есть класс данных, который содержит более десятка членов. Я использую его, чтобы создать диктовку, которую я публикую в ElasticSearch .

Теперь я хочу получить dict от ElasticSearch и использовать его для инициализации класса данных.

С:

  1. Python не позволяет создавать второй __ init __ с другой подписью.
  2. Я не хочу вручную писать __ init __, который генерируется автоматически, просто чтобы добавить необязательный параметр
  3. Я не хочу добавлять необязательный параметр для принятия dict, просто чтобы __ init __ оставался автоматически генерируемым.

Я думал о добавлении второго метода init2 , который будет возвращать экземпляр класса данных и анализировать переданный параметр dict в автоматически сгенерированном методе __ init __.


Я бы оценил ваш вклад, чтобы решить, является ли предложенное ниже решение правильным решением.

Кроме того, может ли эта реализация рассматриваться как тип фабрики?

Спасибо.


Продолжение: поскольку JSON \ словарь, который я получаю из запроса ES, выглядит так:

  1. Имеет те же ключевые слова, что и класс данных

  2. Плоский, то есть вложенных объектов нет.

Я мог бы просто передать значения как ** dict в автоматически сгенерированный метод __ init __.

См. Мой ответ ниже для этого конкретного случая:


from dataclasses import dataclass

@dataclass
class MyData:
    name: str
    age: int = 17

    @classmethod
    def init_from_dict(cls, values_in_dict: dict):
        # Original line using MyData was fixed to use cls, following @ForceBru 's comment
        # return MyData(values_in_dict['name'], age=values_in_dict['age'])
        return cls(values_in_dict['name'], age=values_in_dict['age'])

my_data_1: MyData = MyData('Alice')
print(my_data_1)

my_data_2: MyData = MyData('Bob', 15)
print(my_data_2)

values_in_dict_3: dict = {
    'name': 'Carol',
    'age': 20
}

my_data_3: MyData = MyData.init_from_dict(values_in_dict_3)
print(my_data_3)

# Another init which uses the auto-generated __init__ works in this specific
# case because the values' dict is flat and the keywords are the same as the
# parameter names in the dataclass.
# This allows me to do this
my_data_4: MyData = MyData(**values_in_dict_3)

Ответы [ 3 ]

3 голосов
/ 02 июля 2019

В вашем коде есть потенциальная ошибка.Обратите внимание:

class Thing:
    def __init__(self, a, b):
        self.a, self.b = a, b

    @classmethod
    def from_int(cls, value):
        return Thing(value, value + 1)

class AnotherOne(Thing):
    def __init__(self, a, b):
        self.a, self.b = a + 1, b + 2

Теперь, если вы запустите AnotherOne.from_int(6), вы получите объект Thing:

>>> AnotherOne.from_int(6)
<__main__.Thing object at 0x8f4a04c>

... в то время как вы, вероятно, захотите создать AnotherOne object!

Чтобы исправить это, создайте объект следующим образом:

class Thing:
    ...

    @classmethod
    def from_int(cls, value):
        return cls(value, value + 1)  # Use `cls` instead of `Thing`

Я думаю, что ваш код в порядке: действительно, один из способов использования classmethod предоставляет другойспособы инициализации экземпляра класса, чем использование __init__.

1 голос
/ 02 июля 2019

Кроме того, может ли эта реализация рассматриваться как тип фабрики?

Да, это обычный шаблон для добавления from_<type> classmethod s, так как python не поддерживает перегрузку методов.

0 голосов
/ 04 июля 2019

Как я писал в следующем разделе вопроса, в разделе _source ответа ElasticSearch используются те же ключевые слова, что и у параметров класса данных, и он плоский, что означает, что в JSON \ dict нет вложенных словарей.

Это позволяет мне реализовать следующее.

"_source" моего ответа в упругом поиске выглядит так

response = {
  "_index": "env1",
  "_type": "_doc",
  "_id": "e3c85",
  "_score": 0.105360515,
  "_source": {
    "name": "RaamEEIL",
    "age": "19"
  }
}

Так что я мог бы просто сделать:

my_data = MyData(**response['_source'])

Это передает значения как пары ключ: значение в метод __ init __, и поскольку имена совпадают, все работает гладко.

...