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.
Mettez le code réel dans github.
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 ..
Par exemple, supposons que vous ayez le dict suivant comme source de conversion. C'est une structure qui inclut dict dans dict.
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 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
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 dans
mapping, tels que
src_dict ['value'] ʻ dans cet exemple, sont définis tels qu'ils sont dans la destination de conversion.
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()
Je l'ai fait comme ça.
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)
** 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.
<mapping_root>
.setattr
ou affectez-la comme valeur de dict.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]
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.
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_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()
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.
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