2
votes

Quelle est la bonne façon en Python de définir une classe de données qui a à la fois un __init__ généré automatiquement et un init2 supplémentaire à partir d'un dict de valeurs

En Python, j'ai une classe de données qui contient plus d'une douzaine de membres. Je l'utilise pour créer un dict que je publie dans ElasticSearch .

Maintenant, je veux obtenir un dict à partir d'ElasticSearch et l'utiliser pour initialiser la classe de données. P >

Depuis:

  1. Python ne permet pas de créer un deuxième __ init __ avec une signature différente.
  2. Je ne veux pas écrire manuellement le __ init __ qui est généré automatiquement juste pour ajouter un paramètre facultatif
  3. Je ne veux pas ajouter de paramètre facultatif pour accepter le dict, juste pour que __ init __ reste généré automatiquement.

J'ai pensé à ajouter une deuxième méthode init2 , qui retournera une instance de la classe de données et analysera le paramètre dict passé dans la méthode __ init __ générée automatiquement. p >


J'apprécierais votre contribution pour décider si ma solution suggérée ci-dessous est la bonne implémentation.

De plus, cette implémentation peut-elle être considérée comme un type d'usine?

Merci.


Suivi: puisque le dictionnaire JSON \ que j'obtiens de la requête ES est:

  1. A exactement les mêmes mots-clés que la classe de données

  2. Est plat, i.d., il n'y a pas d'objets imbriqués.

Je pourrais simplement passer les valeurs sous forme de ** dict dans la méthode __ init __ générée automatiquement.

Voir ma réponse ci-dessous pour ce cas spécifique:


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)


0 commentaires

3 Réponses :


4
votes

Il y a un bogue potentiel dans votre code. Considérez ceci:

class Thing:
    ...

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

Maintenant, si vous exécutez AnotherOne.from_int (6) , vous obtiendrez un objet Thing : p >

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

... alors que vous vouliez probablement créer un objet AnotherOne !

Pour résoudre ce problème, créez l'objet comme ceci:

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

Je pense que votre code est par ailleurs très bien: en effet, l'une des utilisations de classmethod est de fournir d'autres moyens d'initialiser une instance d'une classe que d'utiliser __init__.


0 commentaires

1
votes

De plus, cette implémentation peut-elle être considérée comme un type d'usine?

Oui, c'est un modèle courant pour ajouter des from_ classmethod car python ne prend pas en charge la surcharge de méthode.


0 commentaires

1
votes

Comme je l'ai écrit dans la section de suivi de la question, la section _source de la réponse ElasticSearch a les mêmes mots-clés que les paramètres de la classe de données et est plate, ce qui signifie qu'il n'y a pas de dictionnaires imbriqués dans le JSON \ dict.

Cela me permet de mettre en œuvre ce qui suit.

La "_source" de ma réponse dans la recherche élastique ressemble à ceci

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

Je pourrais donc simplement faire:

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


0 commentaires