[Python] Convertit un conteneur à usage général et une classe entre eux

Résumé approximatif

Cela fait un moment, mais c'est la suite de l'article que j'ai écrit plus tôt. Celui que j'ai créé cette fois a été téléchargé sur github. C'est pour moi qu'il peut être installé avec pip.

Ce que je voulais faire

  1. J'ai essayé de traiter la structure de données reçue par json et de la mettre dans DynamoDB avec une autre structure de données.
  2. Fatigué de plus de code qui manipule directement les dictionnaires et les listes. Cela me donne l'impression d'être orienté objet, de vouloir donner un comportement aux données significatives elles-mêmes.
  3. Puis convertissez-le en classe. Convertissons la classe de résultat du traitement en dict.

Ce que j'ai réellement fait

  1. Convertissez la «structure faite de conteneurs à usage général tels que dict et list» en «structure de classe arbitraire». Et vice versa. [^ génériques-conteiners]
  2. J'ai fait un mappage avec dict pour définir la règle de conversion et créé une classe à convertir en fonction de celle-ci.
  3. Un exemple d'utilisation sera présenté dans le test unitaire.

Mettez le code réel dans github.

Faites-le tout de suite

J'ai fait quelque chose de similaire avant, mais le code a changé et le temps a passé, donc j'écrirai à partir de 1 sans casser ..

image

Par exemple, supposons que vous ayez le dict suivant comme source de conversion. C'est une structure qui inclut dict dans dict.

Source de conversion

src_dict = {
    'value' : 'AAA',
    'nested' : {
        'nested_value' : 'BBB'
    }
}

Supposons que vous souhaitiez convertir ceci en une classe comme celle-ci. Je veux avoir une instance de NestedClass dans self.nested de TastClass.

Classe de destination

#Classe 1
class TestClass():
    def test_method(self):
        return 'assigned value: ' + self.value
#Classe 2-Image suspendue dans la partie 1
class NestedTestClass:
    def test_method(self):
        return 'nested assigned value: ' + self.nested_value

Information qui relie deux données

Pour effectuer la conversion ci-dessus, je pense qu'il est nécessaire de mapper quel élément du conteneur est converti dans quelle classe, j'ai donc pensé à ce type de dict.

mapping = {
    '<MAPPING_ROOT>' : TestClass, #Parce que le haut n'a pas de nom'<MAPPING_ROOT>'À
    'nested' : NestedClass
}

dict représente le mappage de [clé de classe / dict]: [fonction]. Le dict lui-même pointé par src_dict n'a pas de nom, utilisez donc<MAPPING_ROOT> ʻ comme clé à la place. Le constructeur est spécifié comme une fonction. Les éléments non inclus dansmapping, tels que src_dict ['value'] ʻ dans cet exemple, sont définis tels qu'ils sont dans la destination de conversion.

Comment utiliser

Je veux l'utiliser comme ça.

usage


#Prendre un mappage dans le constructeur
converter = ObjectConverter(mapping=mapping)
#Transmettez les données de source de conversion à la méthode de conversion
converted_class = converter.convert(src_dict)
#Appeler une méthode de la classe convertie
converted_class.test_method()

la mise en oeuvre

Je l'ai fait comme ça.

Je l'ai fait en tant que converter.py, mais c'est long. Pliez-le.

converter.py



class ObjectConverter:
    #Recevoir la définition de mappage lors de la génération
    def __init__(self, *, mapping):
        self.mapping = mapping

    #Méthode d'appel de conversion
    def convert(self, src):
        #L'élément supérieur est la cartographie'<root>'Prémisse qui correspond toujours
        return self._convert_value('<MAPPING_ROOT>', self.mapping['<MAPPING_ROOT>'], src)

    #Déterminez la méthode de traitement en fonction de la valeur
    def _convert_value(self, key, func, value):
        #Dans le cas d'une liste, tous les éléments sont convertis avec func
        if isinstance(value, (list, tuple)):
            return self._convert_sequence(key, func, value)

        #Dans le cas de dict, récupérez la clé et la valeur telles quelles
        if isinstance(value, dict):
            return self._convert_dict(key, func, value)

        #Pour la classe__dict__Et le traiter comme un dict
        if isinstance(value, object) and hasattr(value, '__dict__'):
            return self._convert_dict(key, func, value.__dict__)

        #Si aucune des conditions ci-dessus ne s'applique, renvoyez-le tel quel
        return value

    #Convertir le contenu de dict
    def _convert_dict(self, key, func, src):
        # _call_Remplissez l'objet créé par la fonction
        return self._assign_dict(self._call_function(key, func, src), key, src)

    #Création de l'objet spécifié par mapping
    def _call_function(self, key, func, src):
        return func()

    #Retirez le contenu de dict et appliquez-le
    def _assign_dict(self, dest, key, src):

        for srcKey, value in src.items():

            #la clé est définie dans le mappage
            if srcKey in self.mapping:
                func = self.mapping[srcKey]
                #Exécuter la fonction mappée et définir le résultat
                self._set_value(dest, srcKey, self._convert_value(srcKey, func, value))

            #Si la clé ne figure pas dans la définition de mappage, définissez-la telle quelle
            else:
                #Ignorer toute valeur définie par mappage dans la valeur transmise ici
                self._set_value(dest, srcKey, value)

        #L'état où le contenu de src est reflété dans created
        return dest

    #Traitement des listes
    def _convert_sequence(self, key, func, sequence):
        current = []
        for value in sequence:
            current.append(self._convert_value(key, func, value))
        return current

    #Définisseur de valeurs pour dict et classe
    def _set_value(self, dest, key, value):
        if isinstance(dest, dict):
            dest[key] = value
        else:
            setattr(dest, key, value)

    #Méthode utilitaire pour obtenir une instance pour la conversion de dict
    #
    @classmethod
    def dict_converter(cls, mapping, *, dict_object=dict):
        reverse_mapping = {}

        #Rendre toutes les destinations cartographiques dict
        for key in mapping.keys():
            reverse_mapping[key] = dict_object

        #Instance à convertir en dict
        return ObjectConverter(mapping=reverse_mapping)

Commentaire grossier

** Voir la source pour les spécifications complètes. ** Mais rien ne se passe, il analyse simplement la valeur qui correspond à la clé contenue dans le mappage. En gros, je fais cela.

Touchez le contenu de la classe

En extrayant «dict» lorsqu'une classe est trouvée, tout ce que vous avez à faire est de scanner le dict. J'aimerais éviter de toucher __dict__ si possible, mais cette fois j'éviterai l'effort d'utiliser d'autres méthodes. Sauf si vous avez affaire à une classe spéciale, il ne devrait y avoir aucun problème. [^ ne-touchez pas ça]

Cas qui ne répond pas à la clé

La valeur non incluse dans le mappage est définie sur l'objet de destination de conversion tel quel, mais même si la valeur à ce moment est dict et qu'il existe une valeur avec le nom inclus dans le mappage, le traitement de mappage est effectué. Ça ne sera pas. Ceci est "dict traité inclus dans le mappage ou inclus dans le mappage ue Je n'aime pas le cas du "traitement de dict", et je pense que la structure de données qui nécessite une telle conversion n'est pas très belle.

conversion de classe en dict

Pour les cas où vous voulez faire une conversion de classe en dict après une conversion de dict en classe pour le restaurer à sa forme d'origine, nous avons une méthode de classe dict_converter pour faciliter l'obtention d'un convertisseur inverse. C'est facile car toutes les destinations de conversion sur le mappage sont définies sur dict. [^ pas-dict-quelque chose]

Test unitaire qui explique également comment utiliser

C'est long, alors pliez-le.

test_objectconverter.py


import unittest
import json
from objectonverter import ObjectConverter

#Classe d'essai 1
class TestClass():
    def test_method(self):
        return 'TestObject.test_method'

#Classe de test partie 2
class NestedTestClass:
    def test_method(self):
        return 'NestedObject.test_method'


class ClassConverterTest(unittest.TestCase):

    #Définissez simplement les propriétés de la classe racine
    def test_object_convert(self):
        dict_data = {
            'value1' : 'string value 1'
        }

        converter = ObjectConverter(mapping={'<MAPPING_ROOT>' : TestClass})
        result = converter.convert(dict_data)
        self.assertEqual(result.value1, 'string value 1')

        #Essayez d'appeler la méthode de la classe générée
        self.assertEqual(result.test_method(), 'TestObject.test_method')

    #Générer une classe imbriquée
    def test_nested_object(self):
        #dict qui mappe les clés et les classes json
        object_mapping = {
            '<MAPPING_ROOT>' : TestClass,
            'nested' : NestedTestClass
        }
        #Source d'origine
        dict_data = {
            'value1' : 'string value 1',
            'nested' : {
                'value' : 'nested value 1'
            }
       }

        converter = ObjectConverter(mapping=object_mapping)
        result = converter.convert(dict_data)
        self.assertEqual(result.value1, 'string value 1')

        self.assertIsInstance(result.nested, NestedTestClass)
        self.assertEqual(result.nested.value, 'nested value 1')

    #Dictez simplement si vous ne spécifiez pas de mappage
    def test_nested_dict(self):
        object_mapping = {
            '<MAPPING_ROOT>' : TestClass
        }

        #Source d'origine
        dict_data = {
            'value1' : 'string value 1',
            'nested' : {
                'value' : 'nested value 1'
            }
        }

        converter = ObjectConverter(mapping = object_mapping)
        result = converter.convert(dict_data)
        self.assertEqual(result.value1, 'string value 1')
        self.assertIsInstance(result.nested, dict)
        self.assertEqual(result.nested['value'], 'nested value 1')

    #Traitement des listes
    def test_sequence(self):
        mapping = {
            '<MAPPING_ROOT>' : TestClass,
            'nestedObjects' : NestedTestClass,
        }
        source_dict = {
            "value1" : "string value 1",
            "nestedObjects" : [
                {'value' : '0'},
                {'value' : '1'},
                {'value' : '2'},
            ]
        }

        converter = ObjectConverter(mapping=mapping)
        result = converter.convert(source_dict)
        self.assertEqual(result.value1, 'string value 1')
        self.assertEqual(len(result.nestedObjects), 3)

        for i in range(3):
            self.assertIsInstance(result.nestedObjects[i], NestedTestClass)
            self.assertEqual(result.nestedObjects[i].value, str(i))

    #Si l'élément racine lui-même est une liste
    def test_root_sequence(self):
        object_mapping = {
            '<MAPPING_ROOT>' : TestClass,
        }

        source_list = [
            {'value' : '0'},
            {'value' : '1'},
            {'value' : '2'},
        ]

        converter = ObjectConverter(mapping=object_mapping)
        result = converter.convert(source_list)

        self.assertIsInstance(result, list)
        self.assertEqual(len(result), 3)

        for i in range(3):
            self.assertIsInstance(result[i], TestClass)
            self.assertEqual(result[i].value, str(i))

    # json -> class -> json
    def test_json_to_class_to_json(self):
        #Fonctions utilisées pour la conversion mutuelle de la classe en json
        def default_method(item):
            if isinstance(item, object) and hasattr(item, '__dict__'):
                return item.__dict__
            else:
                raise TypeError

        #dict qui mappe les clés et les classes json
        object_mapping = {
            '<MAPPING_ROOT>' : TestClass,
            'nested' : NestedTestClass
        }
        #Source d'origine-En une seule ligne pour faciliter la comparaison
        string_data = '{"value1": "string value 1", "nested": {"value": "nested value 1"}}'
        dict_data = json.loads(string_data)

        converter = ObjectConverter(mapping=object_mapping)
        result = converter.convert(dict_data)
        dump_string = json.dumps(result, default=default_method)
        self.assertEqual(dump_string, string_data)

        #Le résultat est le même même s'il est à nouveau converti
        result = converter.convert(json.loads(dump_string))
        self.assertEqual(result.value1, 'string value 1')
        self.assertIsInstance(result.nested, NestedTestClass)
        self.assertEqual(result.nested.value, 'nested value 1')

    #conversion->Conversion inverse
    def test_reverse_convert(self):
        dict_data = {
            'value1' : 'string value 1'
        }
        mapping = {'<MAPPING_ROOT>' : TestClass}

        converter = ObjectConverter(mapping=mapping)
        result = converter.convert(dict_data)
        self.assertEqual(result.value1, 'string value 1')
        
        #Générer un convertisseur de conversion inverse
        reverse_converter = ObjectConverter.dict_converter(mapping=mapping)
        reversed_result = reverse_converter.convert(result)
        self.assertEqual(result.value1, reversed_result['value1'])



if __name__ == '__main__':
    unittest.main()

Commentaire

Eh bien, je me demande si vous pouvez comprendre les bases Il existe un cas de test avec le nom long test_json_to_class_to_json, mais c'est parce que cette classe était à l'origine très consciente de la conversion en json.

Excuse de serpent

Cela fait plus d'un an et demi depuis le dernier article, mais en fait j'ai enfin trouvé un emploi ... Je n'avais pas le temps, donc il était tard.

[^ generic-conteiners]: Comme il est long d'écrire "dict / list" plusieurs fois ci-dessous, écrivez-le comme un conteneur à usage général. Au fait, «classe» est aussi une «instance de classe» pour être exact, mais c'est une «classe» parce qu'elle est longue. [^ json-to-class]: La première chose à laquelle je pensais était de savoir comment gérer json, donc j'ai été piégé par json. Alors que je cherchais des choses, j'ai réalisé que je cherchais en fait des résultats très simples. C'est un flux «quelque chose», mais vous devez réfléchir plus attentivement. [^ dont-touch-this]: C'est facile, alors je vais le faire, mais __dict __ est comme une porte dérobée, et lorsque vous récupérez la valeur de __dict __, __getattribute__ peut ne pas être appelé, donc la classe Il peut se comporter différemment de ce qu'il était initialement prévu. Je n'y penserai pas ici. [^ not-dict-something]: je l'ai écrit pour supporter des types autres que Ichiou dict, mais je ne vois aucune utilité pour cela.

Recommended Posts

[Python] Convertit un conteneur à usage général et une classe entre eux
[Formation Python partie 3] Convertissez les pandas DataFrame, Series et Standard List entre eux
Essayez de convertir les coordonnées de latitude / longitude et du monde entre elles avec python
[Python] Convertit les nombres décimaux en nombres binaires, octaux et hexadécimaux
Convertir / renvoyer des objets de classe au format JSON en Python
Python: variables de classe et d'instance
[python] Convertir la date en chaîne
Convertir numpy int64 en python int
[Python] Convertir la liste en Pandas [Pandas]
Convertir le projet Scratch en Python
Variables de classe et d'instance Python
[Python] Convertir Shift_JIS en UTF-8
Convertir le code python 3.x en python 2.x
[Python] Comment jouer avec les variables de classe avec décorateur et métaclasse
Comment convertir Youtube en mp3 et le télécharger en toute sécurité [Python]
Convertir une vidéo en noir et blanc avec ffmpeg + python + opencv
Déterminez le format de la date et de l'heure avec Python et convertissez-le en Unixtime
Python 3.6 sous Windows ... et vers Xamarin.
[Introduction à Python3 Jour 1] Programmation et Python
Convertir Markdown en PDF en Python
Définitions de classe Python et gestion des instances
Workflow pour convertir une formule (image) en python
Convertir la liste en DataFrame avec python
[Python] Road to the Serpent (3) Classe Python
Lisez le binaire big endian en Python et convertissez-le en ndarray
Python> liste> Convertir une double liste en une seule liste
[Python] Convertit les nombres naturels en nombres ordinaux
objet perl et classe python partie 1.
Journalisation Python et vidage vers json
Convertir décimal en n-aire [python]
Convertissez le résultat de python optparse en dict et utilisez-le
Sélénium et python pour ouvrir Google
J'étais accro aux variables de classe et aux variables d'instance erronées en Python
Python> tuple> Convertir un double tuple en un seul tuple
Lisez le fichier CSV avec Python et convertissez-le en DataFrame tel quel
[Python] Comment convertir un fichier db en csv
Comment empaqueter et distribuer des scripts Python
De Python à l'utilisation de MeCab (et CaboCha)
Convertir un mémo à la fois avec Python 2to3
Convertir Python> séquence de deux valeurs en dictionnaire
Comment installer et utiliser pandas_datareader [Python]
[Python] Comment rendre une classe itérable
Lier des méthodes aux classes et instances Python
[Python] Comment convertir une liste bidimensionnelle en liste unidimensionnelle
Comment convertir Python en fichier exe
[Python] Convertit les délimiteurs de fichier csv en délimiteurs de tabulation
Lier à la classe pour lire et écrire YAML
Convertir un fichier psd en png en Python
Convertir des données Excel en JSON avec python
Convertir Hiragana en Romaji avec Python (bêta)
Fractal pour faire et jouer avec Python
Conversion de katakana en voyelle kana [python]
Convertissez des données FX 1 minute en données 5 minutes avec Python
python> Convertir le tuple en liste> aList = list (pi_tuple)
[Python] Différence entre la méthode de classe et la méthode statique
Portage et modification du solveur de doublets de python2 vers python3.
Lire Python csv et exporter vers txt
python: Comment utiliser les locals () et globals ()