2009-06-24 44 views
19

我試圖將數據從簡單的對象圖轉換爲字典。我不需要類型信息或方法,我不需要能夠再次將它轉換回對象。我發現this question about creating a dictionary from an object's fields,但它不會遞歸地執行它。將python對象圖遞歸轉換爲字典

對於python來說相對來說比較新,我擔心我的解決方案可能會很醜陋,或者不可靠,或者以某種模糊的方式破壞,或者只是普通的NIH。

我的第一次嘗試似乎工作,直到我用列表和字典嘗試它,似乎更容易只是檢查傳遞的對象是否有內部字典,如果不是,只是將其視爲一個值(而不是所有這些都是實例檢查)。我以前的嘗試也沒有遞歸到對象的列表:

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 

這似乎更好地工作,而且不需要例外,但我又真不知道如果在這裏有這樣的情況我不知道它跌倒的地方。

任何建議將不勝感激。

+2

在python也不是那麼不好用異常,有時它可以簡化編碼,一個Python的way- EAFP(更容易請求原諒比許可) – 2009-06-24 04:52:54

+0

特殊情況下,可能是當對象有__slots__,編輯回答 – 2009-06-24 04:56:12

+1

點,但異常的事情是一場神聖的戰爭,我傾向於傾向於他們永遠不會被拋出,除非有什麼是真正的例外,而不是預期的程序流。每個對自己的那一個:) – Shabbyrobe 2009-06-24 04:59:41

回答

27

從阿努拉格Uniyal和倫納特Regebro的答案得出我自己的嘗試和線索的合併最適合我:

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 
5

我不知道檢查basestring或object的目的是什麼? 字典將不會包含任何可調用,除非你有指向這些可調用的屬性,但在這種情況下,不是對象的一部分?

因此,不是檢查各種類型和值,而是讓todict轉換對象,如果引發異常,請使用原始值。

如果obj沒有,todict只會引發異常字典 例如,

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) 

它打印{ 'B1':1, 'B2':2, '01':{ 'A1':1}} 可能會有一些其他的情況需要考慮,但它可能是一個很好的啓動

特殊情況下 如果一個對象使用插槽,那麼你將無法得到字典

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

修復爲槽的情況下可以使用目錄(),而不是直接使用字典

2

在Python有使物體不同略微表現,像元類和諸如此類的東西的許多方面,並它可以覆蓋getattr,從而具有通過dict等無法看到的「神奇」屬性。簡而言之,您無論使用何種方法都不太可能在通用情況下獲得100%完整的圖片。

因此,答案是:如果它在您現在使用的用例中適用於您,那麼代碼是正確的。 ;-)

要稍微更通用的代碼,你可以做這樣的事情:

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    

類似的東西。但是,該代碼沒有經過測試。當您覆蓋getattr時,這仍不能涵蓋此案例,而且我確信還有很多案例不涵蓋,也可能無法保護。 :)

1

一種緩慢而簡單的方法來做到這一點是使用​​對象轉換一個JSON字符串,然後json.loads將其轉換回Python字典:

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

1

我知道這答案是幾年爲時已晚,但我認爲它可能是值得SHA環,因爲它是一個Python 3.3+兼容的修改由@Shabbyrobe原來的解決方案,一向運作良好,對我:

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 

如果你在調用屬性不感興趣,例如,它們可以在剝離字典解析:

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

一個小更新Shabbyrobe的答案,使之成爲namedtuple的工作:

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

一行代碼CONV ERT對象JSON遞歸

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