2009-06-24 10 views
19

Sto provando a convertire i dati da un semplice oggetto grafico in un dizionario. Non ho bisogno di informazioni sui tipi o metodi e non ho bisogno di essere in grado di riconvertirlo nuovamente in un oggetto.Converte ricorsivamente il grafico dell'oggetto python nel dizionario

Ho trovato this question about creating a dictionary from an object's fields, ma non lo fa in modo ricorsivo.

Essendo relativamente nuovo per Python, sono preoccupato che la mia soluzione possa essere brutta, o unpitonica, o rotta in qualche modo oscuro, o semplicemente semplice vecchio NIH.

Il mio primo tentativo è sembrato funzionare fino a quando non l'ho provato con elenchi e dizionari, e mi è sembrato più semplice controllare se l'oggetto passato avesse un dizionario interno e, in caso contrario, trattarlo come un valore (piuttosto che farlo tutto questo è il controllo dell'istanza). I miei tentativi precedenti, inoltre, non hanno ricorsione in elenchi di oggetti:

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 

Questo sembra funzionare meglio e non richiede eccezioni, ma ancora una volta non sono ancora sicuro se ci sono casi in qui io non sono a conoscenza di dove cade.

Qualsiasi suggerimento sarebbe molto apprezzato.

+2

in python non è poi così male a utilizzare le eccezioni e, talvolta, può semplificare la codifica, un modo-divinatorio EAFP (più facile chiedere perdono che il permesso) –

+0

caso speciale potrebbe essere quando oggetto ha __slots__, modificato risposta –

+1

punto preso, ma l'eccezione è un po 'di guerra santa e tendo a preferirli non essere mai lanciati a meno che qualcosa non sia veramente eccezionale, piuttosto che il flusso del programma previsto. ognuno per conto proprio su quello :) – Shabbyrobe

risposta

27

un amalgama di mio tentativo e indizi derivato da Anurag Uniyal e le risposte di Lennart Regebro funziona meglio per me:

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

ben fatto. solo implementazione che funziona esattamente come volevo, finora. –

+0

soluzione elegante! – mvexel

+0

fantastico, mi ha regalato ore della mia vita ... grazie! – pixelphantom

5

Non so quale sia lo scopo del controllo per basestring o oggetto? anche dict non conterrà nessun callable se non si hanno attributi che puntano a tali callables, ma in tal caso non è quella parte dell'oggetto?

così invece di cercare vari tipi e valori, lascia che sia la conversione dell'oggetto e se solleva l'eccezione, usa il valore originale.

todict solleverà eccezione solo se obj non ha dict ad es.

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) 

la stampa { 'B1': 1, 'b2': 2, 'o1': { 'A1': 1}} ci possono essere alcuni altri casi da considerare, ma può essere una buona avviare

casi particolari se un oggetto utilizza slot allora non sarà in grado di ottenere dict esempio

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

correzione per i casi di slot può essere quella di utilizzare dir() anziché direttamente usando l'dict

+0

Grazie per l'aiuto e l'ispirazione. Ho appena realizzato che non gestisce elenchi di oggetti, quindi ho aggiornato la mia versione per testare __iter__. Non sono sicuro se sia una buona idea. – Shabbyrobe

+0

sembra che diventerà più complicato perché ciò che accade per un oggetto che fornisce un iter per iterare un attributo di lista che hai già inserito in dict, può essere una soluzione generale non è possibile. –

2

In Python ci sono molti modi per realizzare oggetti comportano in modo leggermente diverso, come metaclassi e quant'altro, e può ignorare getattr e quindi avere attributi "magici" che non è possibile vedere attraverso dict, ecc. In breve, è improbabile che si ottenga un'immagine completa al 100% nel caso generico con qualsiasi metodo si usi .

Pertanto, la risposta è: se funziona per voi nel caso d'uso che avete ora, allora il codice è corretto. ;-)

per rendere il codice un po 'più generico si potrebbe fare qualcosa di simile:

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    

Qualcosa del genere.Quel codice non è tuttavia testato. Questo non copre ancora il caso quando si sostituisce getattr, e sono sicuro che ci sono molti più casi che non copre e potrebbe non essere copribile. :)

1

Un modo lento ma semplice per farlo è quello di utilizzare jsonpickle per convertire l'oggetto in una stringa JSON e poi json.loads per riconvertirlo in un dizionario Python:

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

1

mi rendo conto che questa risposta è a pochi anni di ritardo, ma ho pensato che potrebbe valere la pena di sha Anello in quanto si tratta di un Python 3.3+ compatibile modifica alla soluzione originale @Shabbyrobe che ha generalmente funzionato bene per me:

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 

Se non sei interessato negli attributi callable, per esempio, possono essere rimossi in comprensione dizionario:

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

Un piccolo aggiornamento alla risposta di Shabbyrobe per farlo funzionare per namedtuple s:

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

un codice linea per conv ert oggetto JSON ricorsivamente

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