1
votes

Décorateur vers liste imbriquée de classes de données en Python

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


2 commentaires

key2 devrait être une liste, non? Je ne pense pas que votre déclaration de data soit correcte


Droite, modification de l'article, merci de l'avoir signalé


3 Réponses :


0
votes

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


1 commentaires

Le problème principal est que List[level2] n'est pas une data_class sorte que l'instruction if n'est pas utilisée.



1
votes

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]


3 commentaires

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



1
votes

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


0 commentaires