51 Stimmen

Rekursive Konvertierung eines Python-Objektgraphen in ein Wörterbuch

Ich versuche, die Daten aus einem einfachen Objektdiagramm in ein Wörterbuch zu konvertieren. Ich brauche keine Typinformationen oder Methoden und muss nicht in der Lage sein, sie wieder in ein Objekt zu konvertieren.

Ich fand diese Frage zur Erstellung eines Wörterbuchs aus den Feldern eines Objekts aber er tut es nicht rekursiv.

Da ich relativ neu in Python bin, bin ich besorgt, dass meine Lösung hässlich, oder unpythonisch, oder in irgendeiner obskuren Weise gebrochen, oder einfach nur alt NIH sein kann.

Mein erster Versuch schien zu funktionieren, bis ich es mit Listen und Wörterbüchern versuchte, und es schien einfacher, nur zu prüfen, ob das übergebene Objekt ein internes Wörterbuch hatte, und wenn nicht, es einfach als Wert zu behandeln (anstatt all das zu tun, isinstance Überprüfung). Meine früheren Versuche haben auch nicht in Listen von Objekten rekursiert:

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

Dies scheint besser zu funktionieren und erfordert keine Ausnahmen, aber auch hier bin ich mir nicht sicher, ob es Fälle gibt, die ich nicht kenne, in denen dies nicht funktioniert.

Für Vorschläge wären wir sehr dankbar.

63voto

Shabbyrobe Punkte 11820

Eine Mischung aus meinem eigenen Versuch und Hinweisen aus den Antworten von Anurag Uniyal und Lennart Regebro funktioniert für mich am besten:

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

39voto

Archit Dwivedi Punkte 386

Eine Codezeile zur rekursiven Umwandlung eines Objekts in JSON.

import json

def get_json(obj):
  return json.loads(
    json.dumps(obj, default=lambda o: getattr(o, '__dict__', str(o)))
  )

obj = SomeClass()
print("Json = ", get_json(obj))

9voto

Anurag Uniyal Punkte 81337

Ich weiß nicht, was ist der Zweck der Überprüfung für basestring oder Objekt ist? auch Diktat enthält keine Callables, es sei denn, Sie haben Attribute, die auf solche Callables zeigen, aber ist das in diesem Fall nicht Teil von object?

Anstatt also auf verschiedene Typen und Werte zu prüfen, lassen Sie todict das Objekt konvertieren, und wenn es die Ausnahme auslöst, verwenden Sie den ursprünglichen Wert.

todict löst nur dann eine Ausnahme aus, wenn obj nicht die Eigenschaft Diktat z.B..

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)

Es druckt {'b1': 1, 'b2': 2, 'o1': {'a1': 1}} Es gibt vielleicht noch andere Fälle, die zu berücksichtigen sind, aber das ist ein guter Anfang

Sonderfälle Wenn ein Objekt Slots verwendet, können Sie keine Diktat z.B..

class A(object):
    __slots__ = ["a1"]
    def __init__(self):
        self.a1 = 1

Die Lösung für die Slot-Fälle kann darin bestehen, dir() zu verwenden, anstatt direkt die Diktat

5voto

Tom Punkte 17812

Ein langsamer, aber einfacher Weg, dies zu tun, ist die Verwendung von jsonpickle um das Objekt in eine JSON-Zeichenkette zu konvertieren und dann json.loads um es wieder in ein Python-Wörterbuch zu konvertieren:

dict = json.loads(jsonpickle.encode( obj, unpicklable=False ))

4voto

hbristow Punkte 1826

Ich weiß, dass diese Antwort ein paar Jahre zu spät ist, aber ich dachte, es könnte wert sein, zu teilen, da es eine Python 3.3+ kompatible Änderung der ursprünglichen Lösung von @Shabbyrobe ist, die im Allgemeinen gut für mich gearbeitet hat:

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

Wenn Sie z. B. nicht an aufrufbaren Attributen interessiert sind, können diese im Wörterbuchverständnis entfernt werden:

elif isinstance(obj, dict):
  return dict((key, todict(val)) for key, val in obj.items() if not callable(val))

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X