2009-06-24 13 views
19

Próbuję przekonwertować dane z prostego wykresu obiektów do słownika. Nie potrzebuję informacji o typie ani metod i nie muszę mieć możliwości ponownego przekonwertowania ich z powrotem na obiekt.Rekurencyjnie przekonwertuj wykres obiektu Pythona do słownika

Znalazłem this question about creating a dictionary from an object's fields, ale nie robi tego rekurencyjnie.

Będąc stosunkowo nowym pytonem, jestem zaniepokojony, że moje rozwiązanie może być brzydkie, niepytikonowe lub zepsute w jakiś niejasny sposób, lub po prostu stary NIH.

Moja pierwsza próba zadziałała, dopóki nie wypróbowałem jej z listami i słownikami, i wydawało mi się łatwiej po prostu sprawdzić, czy obiekt przeszedł z wewnętrznym słownikiem, a jeśli nie, po prostu potraktować to jako wartość (zamiast robić to wszystko, co sprawdza instynktownie). Moje dotychczasowe próby również nie rekursja do listy obiektów:

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 

To wydaje się działać lepiej i nie wymagają wyjątków, ale znowu ja nadal nie jestem pewien, czy istnieją przypadki tutaj nie jestem świadomy gdzie spadnie.

Wszelkie sugestie będą mile widziane.

+2

w python nie jest tak źle w użyciu wyjątki i czasami może to uproszczenie kodowania, a pythonic drodze, EAFP (Łatwiej prosić o przebaczenie niż uprawnienia) –

+0

szczególny przypadek mógłby być, gdy obiekt ma __slots__, zredagowana odpowiedź: –

+1

punkt wzięty, ale wyjątek jest trochę świętej wojny i mam tendencję do preferowania, aby nigdy nie były rzucane, chyba że coś jest naprawdę wyjątkowe, a nie oczekiwany przepływ programu. każdy na swój własny na tym :) – Shabbyrobe

Odpowiedz

27

połączeniem własnej próbie i wskazówki pochodzące od Anuraga Uniyal i odpowiedzi Lennart Regebro działa najlepiej dla mnie:

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

ładnie wykonane. tylko wdrożenie, które działa tak, jak tego chciałem, do tej pory. –

+0

eleganckie rozwiązanie! – mvexel

+0

niesamowite, po prostu dał mi wiele godzin mojego życia z powrotem ... dzięki! – pixelphantom

5

Nie wiem, jaki jest cel sprawdzania, czy nie ma linii bazowej lub obiektu? również dict nie będzie zawierał żadnych animacji, chyba że masz atrybuty wskazujące na takie podpórki, ale w takim przypadku nie jest to część obiektu?

więc zamiast sprawdzać różne typy i wartości, pozwól todict przekonwertować obiekt, a jeśli podnosi wyjątek, użytkownik oryginalną wartość.

todict wywoła tylko wyjątek, jeśli obiekt nie ma dict np.

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) 

drukuje { 'b1': 1, 'B2': 2 'o1': { 'a1': 1}} mogą istnieć inne sprawy do rozważenia, ale może to być dobry rozpocząć

szczególne przypadki jeśli obiekt wykorzystuje szczeliny wtedy nie będzie w stanie uzyskać dict np

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

fix przypadków szczeliny mogą być w użyciu dir() zamiast bezpośrednio za pomocą dict

+0

Dzięki za pomoc i inspirację. Właśnie zdałem sobie sprawę, że nie obsługuje list obiektów, więc zaktualizowałem moją wersję, aby przetestować __iter__. Nie jestem pewien, czy to dobry pomysł. – Shabbyrobe

+0

wygląda na to, że będzie trudniejsze, ponieważ to, co dzieje się dla obiektu, który zapewnia iterację atrybutu listy, który już umieściłeś w dyktafonie, może być rozwiązaniem ogólnym, nie jest możliwe. –

2

W Pythonie istnieje wiele sposobów dokonywania obiekty zachowują się nieco inaczej, jak metaclasses i etażerka, a może przesłonić getattr i tym samym mieć "magiczne" atrybuty, których nie można przejrzeć przez dict itp. Krótko mówiąc, jest mało prawdopodobne, że otrzymasz pełny obraz w ogólnym przypadku z dowolną metodą, której używasz .

W związku z tym odpowiedź brzmi: jeśli zadziała w danym przypadku użycia, kod jest prawidłowy. ;-)

Aby kodu nieco bardziej ogólny można zrobić coś takiego:

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    

coś takiego.Ten kod jest nietestowany. To nadal nie obejmuje przypadku, gdy zastępujesz getattr i jestem pewien, że jest o wiele więcej przypadków, których nie obejmuje i może nie być możliwe. :)

1

Powolny ale łatwy sposób to zrobić jest użycie jsonpickle konwertować obiekt na ciąg JSON, a następnie json.loads przekonwertować go z powrotem do słownika Pythona:

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

1

Zdaję sobie sprawę, że ta odpowiedź jest o kilka lat za późno, ale pomyślałem, że może warto sha pierścień, ponieważ jest to Python 3.3+ kompatybilny modyfikacja oryginalnego roztworu przez @Shabbyrobe że system funkcjonuje dobrze dla mnie:

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 

Jeśli nie jesteś zainteresowany płatnych na żądanie atrybutów, na przykład, mogą być pozbawiony w Słownik zrozumieniem:

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

mała aktualizacja do odpowiedzi Shabbyrobe jest, aby pracować dla 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

jeden kod linia do Conv ert obiekt do JSON rekurencyjnie

import json 
print(json.dumps(a, default=lambda o: getattr(o, '__dict__', str(o)))) 
Powiązane problemy