2009-06-24 11 views
19

Ich versuche, die Daten aus einem einfachen Objektdiagramm in ein Wörterbuch zu konvertieren. Ich brauche keine Typinformationen oder Methoden und muss sie nicht wieder in ein Objekt konvertieren können.Recursively Python Objektgraph konvertieren Zum Wörterbuch

fand ich this question about creating a dictionary from an object's fields, aber es ist es nicht rekursiv tun.

Da ich relativ neu zu Python bin, mache ich mir Sorgen, dass meine Lösung hässlich oder unpythonisch oder in irgendeiner obskuren Weise oder einfach nur NIH gebrochen sein könnte.

Mein erster Versuch schien zu funktionieren, bis ich es mit Listen und Wörterbüchern versuchte, und es schien leichter zu überprüfen, ob das übergebene Objekt ein internes Wörterbuch hatte, und wenn nicht, einfach als Wert zu behandeln (anstatt zu tun) all das überprüft zB). Meine bisherigen Versuche haben Rekursion auch nicht in Listen von Objekten:

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 

Diese besser zu funktionieren scheint und erfordert keine Ausnahmen, aber wieder ich bin noch nicht sicher, ob es hier Fälle, die ich bin mir nicht bewusst von wo es fällt.

Alle Vorschläge würden sehr geschätzt werden.

+2

in Python ist es nicht so schlimm Ausnahmen zu verwenden, und manchmal kann es die Codierung, eine pythonic Weg- EAFP (Einfachere Vergebung zu bitten als Permission) vereinfacht –

+0

Sonderfall könnte sein, wenn Objekt __slots__, bearbeitete Antwort –

+1

Punkt genommen, aber die Ausnahme Sache ist ein bisschen ein heiliger Krieg und ich tendiere dazu, sie zu bevorzugen, nie geworfen zu werden, es sei denn, etwas ist wirklich außergewöhnlich, eher als erwartete Programmfluss. jeder zu ihrem eigenen auf diesem einen :) – Shabbyrobe

Antwort

27

ein Zusammenschluss von meinem eigenen Versuch und Hinweisen abgeleitet von Anurag Uniyal und Lennart Regebro der Antworten am besten für mich:

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__"): 
     return [todict(v, classkey) for v in obj] 
    elif hasattr(obj, "__dict__"): 
     data = dict([(key, todict(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 
+0

schön gemacht. einzige Implementierung, die so funktioniert, wie ich es wollte, bis jetzt. –

+0

elegante Lösung! – mvexel

+0

genial, gab mir nur Stunden meines Lebens zurück ... danke! – pixelphantom

5

Ich weiß nicht, was der Zweck für basestring oder Objekt zu prüfen ist? auch dict werden keine Callables enthalten, wenn Sie Attribute Hinweis auf solche Callables, aber in diesem Fall ist nicht, dass ein Teil des Objekts?

so stattdessen für verschiedene Typen und Werte zu überprüfen, lassen todict das Objekt konvertieren und wenn es stellt sich die Ausnahme, Benutzer den ursprünglichen Wert.

todict nur Ausnahme auslösen, wenn obj nicht hat dict 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) 

druckt { 'b1': 1, 'b2': 2, 'o1': { 'a1': 1}} kann es einige andere Fälle zu prüfen sein, aber es kann eine gute sein Start

Sonderfälle , wenn ein Objekt Slots verwendet, dann werden Sie nicht dict zB bekommen können,

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

fix für die Schlitze Fällen kann dir() verwenden, anstatt direkt mit dem dict

+0

Danke für die Hilfe und Inspiration. Ich habe gerade festgestellt, dass es keine Listen von Objekten behandelt, also habe ich meine Version aktualisiert, um auf __iter__ zu testen. Nicht sicher, ob das eine gute Idee ist. – Shabbyrobe

+0

sieht aus wie es schwieriger wird, denn was passiert für ein Objekt, das ein Iter bietet, um ein Listenattribut zu iterieren, das Sie bereits in dict eingegeben haben, kann allgemeine Lösung nicht möglich sein. –

2

In Python gibt es viele Möglichkeiten, Objekte etwas anders verhalten, wie metaclasses und so weiter, und Es kann getattr überschreiben und dadurch "magische" Attribute haben, die Sie durch dict usw. nicht sehen können. Kurz gesagt, ist es unwahrscheinlich, dass Sie ein 100% komplettes Bild im generischen Fall mit welcher Methode auch immer erhalten werden .

Daher lautet die Antwort: Wenn es für Sie in den Anwendungsfall arbeitet die Sie jetzt haben, dann ist der Code korrekt ist. ;-)

Um etwas allgemeineren Code machen Sie so etwas tun könnte:

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    

So ähnlich.Dieser Code ist jedoch nicht getestet. Dies deckt immer noch nicht den Fall, wenn Sie getattr überschreiben, und ich bin sicher, es gibt viele weitere Fälle, die es nicht abdecken und möglicherweise nicht abdeckbar ist. :)

1

Eine langsame, aber einfache Möglichkeit, dies zu tun, ist jsonpickle zu verwenden, um das Objekt zu konvertieren

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

1

Mir ist klar, dass diese Antwort ist ein paar Jahre zu spät, aber ich dachte, es wert sha sein könnte: in einen String JSON und dann json.loads, um es zu einem python-Wörterbuch zu konvertieren zurück Ring da es ein Python 3.3+ kompatibel Änderung der ursprünglichen Lösung von @Shabbyrobe, die gut für mich im Allgemeinen 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 in aufrufbar Attribute nicht interessiert sind, zum Beispiel, können sie in die ausgezogen werden Wörterbuch Verständnis:

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

Ein kleines Update zu Shabbyrobe Antwort auf es für namedtuple s funktioniert:

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 
1

eine Zeile Code conv ert Objekt json rekursiv

import json 
print(json.dumps(a, default=lambda o: getattr(o, '__dict__', str(o)))) 
Verwandte Themen