Conversion récursive d'un graphe d'objet python en dictionnaire
-
10-07-2019 - |
Question
J'essaie de convertir les données d'un graphe d'objet simple en dictionnaire. Je n'ai pas besoin d'informations de type ou de méthodes et je n'ai pas besoin de pouvoir le reconvertir en objet.
J'ai trouvé cette question sur la création d'un dictionnaire à partir des champs d'un objet , mais il ne le fait pas récursivement.
Étant relativement nouveau sur Python, je crains que ma solution ne soit laide, ou non rythmique, ou brisée de façon obscure, ou tout simplement un vieil NIH.
Ma première tentative a semblé marcher jusqu'à ce que je l'utilise avec des listes et des dictionnaires. Il semblait plus simple de vérifier si l'objet transmis avait un dictionnaire interne et, dans le cas contraire, de le traiter comme une valeur (plutôt que de tout c'est la vérification d'instance). Mes tentatives précédentes ne se sont pas non plus reproduites dans les listes d'objets:
def todict(obj):
if hasattr(obj, "__iter__"):
return [todict(v) for v in obj]
elif hasattr(obj, "__dict__"):
return dict([(key, todict(value))
for key, value in obj.__dict__.iteritems()
if not callable(value) and not key.startswith('_')])
else:
return obj
Cela semble mieux fonctionner et ne nécessite pas d'exception, mais encore une fois, je ne suis toujours pas sûr s'il y a des cas que je ne connais pas où cela tombe.
Toute suggestion serait la bienvenue.
La solution
Une fusion de ma propre tentative et des indices dérivés des réponses d’Anurag Uniyal et de Lennart Regebro me conviennent mieux:
def todict(obj, classkey=None):
if isinstance(obj, dict):
data = {}
for (k, v) in obj.items():
data[k] = todict(v, classkey)
return data
elif hasattr(obj, "_ast"):
return todict(obj._ast())
elif hasattr(obj, "__iter__") and not isinstance(obj, str):
return [todict(v, classkey) for v in obj]
elif hasattr(obj, "__dict__"):
data = dict([(key, todict(value, classkey))
for key, value in obj.__dict__.items()
if not callable(value) and not key.startswith('_')])
if classkey is not None and hasattr(obj, "__class__"):
data[classkey] = obj.__class__.__name__
return data
else:
return obj
Autres conseils
Une ligne de code permettant de convertir un objet au format JSON de manière récursive.
import json
print(json.dumps(a, default=lambda o: getattr(o, '__dict__', str(o))))
Je ne sais pas à quoi sert le contrôle de basestring ou d'objet? De même, dict ne contiendra pas de callable, à moins que vous ne possédiez des attributs pointant vers de tels callables, mais dans ce cas, cela ne fait-il pas partie de l'objet?
donc au lieu de rechercher différents types et valeurs, laissez todict convertir l'objet et, s'il déclenche l'exception, indiquez à l'utilisateur la valeur initiale.
todict ne lèvera une exception que si obj n'a pas dict par exemple
class A(object):
def __init__(self):
self.a1 = 1
class B(object):
def __init__(self):
self.b1 = 1
self.b2 = 2
self.o1 = A()
def func1(self):
pass
def todict(obj):
data = {}
for key, value in obj.__dict__.iteritems():
try:
data[key] = todict(value)
except AttributeError:
data[key] = value
return data
b = B()
print todict(b)
il imprime {'b1': 1, 'b2': 2, 'o1': {'a1': 1}} il y a peut-être d'autres cas à considérer, mais cela peut être un bon début
cas particuliers Si un objet utilise des emplacements, vous ne pourrez pas obtenir dict , par exemple.
class A(object):
__slots__ = ["a1"]
def __init__(self):
self.a1 = 1
la solution pour les cas de machine à sous peut être d'utiliser dir () au lieu d'utiliser directement le dict
En Python, il existe de nombreuses façons de faire en sorte que les objets se comportent légèrement différemment, comme les métaclasses ou autres, et ils peuvent remplacer getattr et avoir ainsi "magique". attributs que vous ne pouvez pas voir à travers dict , etc. En bref, il est peu probable que vous obteniez une image complète à 100% dans le cas générique, quelle que soit la méthode que vous utilisez.
Par conséquent, la réponse est la suivante: si cela fonctionne pour vous dans le cas d'utilisation que vous avez maintenant, le code est correct. ; -)
Pour créer un code un peu plus générique, vous pouvez faire quelque chose comme ceci:
import types
def todict(obj):
# Functions, methods and None have no further info of interest.
if obj is None or isinstance(subobj, (types.FunctionType, types.MethodType))
return obj
try: # If it's an iterable, return all the contents
return [todict(x) for x in iter(obj)]
except TypeError:
pass
try: # If it's a dictionary, recurse over it:
result = {}
for key in obj:
result[key] = todict(obj)
return result
except TypeError:
pass
# It's neither a list nor a dict, so it's a normal object.
# Get everything from dir and __dict__. That should be most things we can get hold of.
attrs = set(dir(obj))
try:
attrs.update(obj.__dict__.keys())
except AttributeError:
pass
result = {}
for attr in attrs:
result[attr] = todict(getattr(obj, attr, None))
return result
Quelque chose comme ça. Ce code n'a pas encore été testé. Cela ne couvre toujours pas le cas lorsque vous remplacez getattr , et je suis sûr qu'il existe de nombreux autres cas non couverts et susceptibles de ne pas être couverts. :)
Une méthode lente mais facile consiste à utiliser jsonpickle
pour convertir l'objet en chaîne JSON, puis json.loads
pour le reconvertir en dictionnaire python. :
dict = json.loads (jsonpickle.encode (obj, unpicklable = False))
Je me rends compte que cette réponse a quelques années de retard, mais j’ai pensé que cela valait la peine de la partager car c’est une modification compatible avec Python 3.3+ de la solution originale de @Shabbyrobe qui a généralement bien fonctionné pour moi:
import collections
try:
# Python 2.7+
basestring
except NameError:
# Python 3.3+
basestring = str
def todict(obj):
"""
Recursively convert a Python object graph to sequences (lists)
and mappings (dicts) of primitives (bool, int, float, string, ...)
"""
if isinstance(obj, basestring):
return obj
elif isinstance(obj, dict):
return dict((key, todict(val)) for key, val in obj.items())
elif isinstance(obj, collections.Iterable):
return [todict(val) for val in obj]
elif hasattr(obj, '__dict__'):
return todict(vars(obj))
elif hasattr(obj, '__slots__'):
return todict(dict((name, getattr(obj, name)) for name in getattr(obj, '__slots__')))
return obj
Si les attributs appelables, par exemple, ne vous intéressent pas, vous pouvez les supprimer dans la compréhension du dictionnaire:
elif isinstance(obj, dict):
return dict((key, todict(val)) for key, val in obj.items() if not callable(val))
Une petite mise à jour de la réponse de Shabbyrobe pour que cela fonctionne avec les noms
:
def obj2dict(obj, classkey=None):
if isinstance(obj, dict):
data = {}
for (k, v) in obj.items():
data[k] = obj2dict(v, classkey)
return data
elif hasattr(obj, "_asdict"):
return obj2dict(obj._asdict())
elif hasattr(obj, "_ast"):
return obj2dict(obj._ast())
elif hasattr(obj, "__iter__"):
return [obj2dict(v, classkey) for v in obj]
elif hasattr(obj, "__dict__"):
data = dict([(key, obj2dict(value, classkey))
for key, value in obj.__dict__.iteritems()
if not callable(value) and not key.startswith('_')])
if classkey is not None and hasattr(obj, "__class__"):
data[classkey] = obj.__class__.__name__
return data
else:
return obj
def list_object_to_dict(lst):
return_list = []
for l in lst:
return_list.append(object_to_dict(l))
return return_list
def object_to_dict(object):
dict = vars(object)
for k,v in dict.items():
if type(v).__name__ not in ['list', 'dict', 'str', 'int', 'float']:
dict[k] = object_to_dict(v)
if type(v) is list:
dict[k] = list_object_to_dict(v)
return dict