Je voudrais créer un dict à une dataclass qui contient comme liste de dataclass comme attribut
Voici un exemple de ce que j'aimerais réaliser:
def is_dataclass(obj): """Returns True if obj is a dataclass or an instance of a dataclass.""" _FIELDS = '__dataclass_fields__' return hasattr(obj, _FIELDS) def nested_dataclass(*args, **kwargs): def wrapper(cls): cls = dataclass(cls, **kwargs) original_init = cls.__init__ def __init__(self, *args, **kwargs): for name, value in kwargs.items(): field_type = cls.__annotations__.get(name, None) if is_dataclass(field_type) and isinstance(value, dict): new_obj = field_type(**value) kwargs[name] = new_obj original_init(self, *args, **kwargs) cls.__init__ = __init__ return cls return wrapper(args[0]) if args else wrapper
Le décorateur le plus proche que j'ai trouvé était celui-ci, mais il ne fonctionne pas avec les listes de classes de données: Création d'objets de classe de données imbriqués en Python
from typing import List from dataclasses import dataclass @dataclass class level2: key21: int key22: int @nested_dataclass class level1: key1: int key2: List[level2] data = { 'key1': value1, 'key2': [{ 'key21': value21, 'key22': value22, }] } my_object = level1(**data) print(my_object.key2[0].key21) #should print value21
Comment modifieriez-vous ce décorateur ou en créeriez-vous un qui ferait l'affaire? (Je n'ai aucune expérience en décoration de bâtiment)
Tout commentaire / code est très apprécié. Merci
3 Réponses :
Cela ne fournit pas comment changer le décorateur et si vous ne souhaitez pas utiliser de packages tiers, veuillez ignorer cette réponse. Mais je pense que pydantic
peut faire ce que vous voulez. La seule raison que je suggère est que cela ne vous permettra pas d'avoir par erreur key2
comme dictionnaire quand il a été déclaré comme une liste.
class level1(BaseModel): key1: int key2: level2 # Not a list data = { 'key1': 1, 'key2': { 'key21': 21, 'key22': 22, } } my_object = level1(**data) print(my_object.key2.key21) # prints 21
Si vous vouliez réellement avoir key21
directement accessible à partir de key2
alors
from typing import List from pydantic import BaseModel class level2(BaseModel): key21: int key22: int class level1(BaseModel): key1: int key2: List[level2] data = { 'key1': 1, 'key2': [{ 'key21': 21, 'key22': 22, }] } my_object = level1(**data) print(my_object.key2[0].key21) # prints 21
Encore une fois, ignorez cela si votre objectif est de réussir à faire travailler le décorateur. Sinon, l'installation de pydantic ne fera pas de mal :)
Le problème principal est que List[level2]
n'est pas une data_class
sorte que l'instruction if
n'est pas utilisée.
D'accord donc j'ai un peu changé le décorateur mais c'est très spécifique à l'exemple fourni ici. Le principal problème était que votre champ List[level2]
n'était pas une dataclass
. Donc pour faire le tour, j'ai joué un peu et j'ai remarqué qu'il y avait une propriété args qui pouvait vous indiquer le type imbriqué dans la liste. Je n'ai jamais travaillé avec des classes de données auparavant (sauf avec pydantic), alors peut-être qu'il y a une meilleure réponse là-bas
level3(key3=[level1(key1=1, key2=[level2(key21=21, key22=22), level2(key21=23, key22=24)])])
Nidification supplémentaire
@nested_dataclass class level3: key3: List[level1] level3(**{'key3': [data]})
Production:
def nested_dataclass(*args, **kwargs): def wrapper(cls): cls = dataclass(cls, **kwargs) original_init = cls.__init__ def __init__(self, *args, **kwargs): for name, value in kwargs.items(): field_type = cls.__annotations__.get(name, None) if hasattr(field_type, '__args__'): inner_type = field_type.__args__[0] if is_dataclass(inner_type): new_obj = [inner_type(**dict_) for dict_ in value] kwargs[name] = new_obj original_init(self, *args, **kwargs) cls.__init__ = __init__ return cls return wrapper(args[0]) if args else wrapper @dataclass class level2: key21: int key22: int @nested_dataclass class level1: key1: int key2: List[level2] data = { 'key1': 1, 'key2': [{ 'key21': 21, 'key22': 22, }, { 'key21': 23, 'key22': 24 }] } my_object = level1(**data) print(my_object.key2[0].key21) #should print 21 print(my_object.key2[1].key21) #should print 23 @nested_dataclass class random: key1: int key2: List[int] random_object = random(**{'key1': 1, 'key2': [1,2,3]}) print(random_object.key2) # prints [1,2,3]
Je viens de trouver un cas où cela ne fonctionne pas: dataclass class level3: key31: int nested_dataclass class level2: key21: level3 nested_dataclass class level1: key11: List [level2] data = {'key11': [{'key21': {'key31 ': 21,}}]} mon_objet = niveau1 (** données) print (mon_objet) Cependant, si la classe nested_dataclass level2: key21: List [level3] fonctionne. Je ne comprends pas vraiment pourquoi. Des pensées?
La solution que j'ai fournie est très spécifique au cas comme mentionné. Votre nouveau level2
n'est pas imbriqué comme dans les cas ci-dessus où l'imbrication est uniquement pour une List
d'autres classes de données. Vous pouvez modifier le code que je devais travailler pour vous, mais pour le généraliser, vous aurez besoin de beaucoup plus de code que cela. Donc, encore une fois, je suggère d'utiliser pydantic
Merci @ Buckeye14Guy, essaiera de le faire fonctionner et affichera la solution généralisée
pip installer validé-dc
ValidatedDC: https://github.com/EvgeniyBurdin/validated_dc
from dataclasses import dataclass from typing import List, Union from validated_dc import ValidatedDC @dataclass class Level2(ValidatedDC): key21: int key22: int @dataclass class Level1(ValidatedDC): key1: int key2: List[Level2] data = { 'key1': 1, 'key2': [{ 'key21': 21, 'key22': 22, }] } my_object = Level1(**data) assert my_object.key2[0].key21 == 21 # ---------------------------------- @dataclass class Level1(ValidatedDC): key1: int key2: Union[Level2, List[Level2]] my_object = Level1(**data) # key2 - list assert my_object.key2[0].key21 == 21 data['key2'] = { 'key21': 21, 'key22': 22, } my_object = Level1(**data) # key2 - dict assert my_object.key2.key21 == 21
key2 devrait être une liste, non? Je ne pense pas que votre déclaration de
data
soit correcteDroite, modification de l'article, merci de l'avoir signalé