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.

Était-ce utile?

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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top