递归地将python对象图转换为字典

时间:2009-06-24 04:30:36

标签: python python-2.6

我正在尝试将数据从简单的对象图转换为字典。我不需要类型信息或方法,我不需要能够再次将它转换回对象。

我找到了this question about creating a dictionary from an object's fields,但它没有递归。

对于python而言比较陌生,我担心我的解决方案可能是丑陋的,或者说是unpythonic,或者以某种模糊的方式打破,或者只是普通的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

这似乎工作得更好,不需要例外,但我仍然不确定这里是否有案例我不知道它在哪里落下。

我们非常感谢任何建议。

10 个答案:

答案 0 :(得分:40)

我自己的尝试和来自Anurag Uniyal和Lennart 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__") 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

答案 1 :(得分:6)

我不知道检查basetring或object的目的是什么? dict 也不会包含任何可调用项,除非你有指向这些callables的属性,但在那种情况下不是对象的那部分?

所以不是检查各种类型和值,而是让todict转换对象,如果它引发了异常,请使用原始值。

如果obj没有 dict

todict只会引发异常 e.g。

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,'o1':{'a1':1}} 可能还有其他一些案例要考虑,但这可能是一个好的开始

特殊情况 如果一个对象使用了插槽,那么你将无法获得 dict ,例如

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

修复插槽案例可以使用dir()而不是直接使用 dict

答案 2 :(得分: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 时,这仍然不包括这种情况,而且我确信还有更多的情况,它不会覆盖,也可能不会令人难以忍受。 :)

答案 3 :(得分:1)

缓慢但简单的方法是使用jsonpickle将对象转换为JSON字符串,然后json.loads将其转换回python字典:

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

答案 4 :(得分:1)

我意识到这个答案已经太晚了几年,但我认为它可能值得分享,因为它与@Shabbyrobe的原始解决方案的Python 3.3+兼容修改一般对我来说很好:< / p>

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))

答案 5 :(得分: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

答案 6 :(得分:0)

def list_object_to_dict(lst):
    return_list = []
    for l in lst:
        return_list.append(object_to_dict(l))
    return return_list

def object_to_dict(object):
    dict = vars(object)
    for k,v in dict.items():
        if type(v).__name__ not in ['list', 'dict', 'str', 'int', 'float']:
                dict[k] = object_to_dict(v)
        if type(v) is list:
            dict[k] = list_object_to_dict(v)
    return dict

答案 7 :(得分:0)

查看了所有解决方案,@ hbristow的回答与我所寻找的最接近。 添加了enum.Enum处理,因为这会导致RecursionError: maximum recursion depth exceeded错误,并且将对__slots__的对象进行重新排序以使对象优先定义__dict__

def todict(obj):
  """
  Recursively convert a Python object graph to sequences (lists)
  and mappings (dicts) of primitives (bool, int, float, string, ...)
  """
  if isinstance(obj, str):
    return obj
  elif isinstance(obj, enum.Enum):
    return str(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, '__slots__'):
    return todict(dict((name, getattr(obj, name)) for name in getattr(obj, '__slots__')))
  elif hasattr(obj, '__dict__'):
    return todict(vars(obj))
  return obj

答案 8 :(得分:0)

不需要自定义实现。可以使用 jsons 库。

import jsons

object_dict = jsons.dump(object_instance)

答案 9 :(得分:0)

我会对接受的答案发表评论,但我的代表不够高...... 接受的答案很好,但在 elif 之后添加另一个 if 以支持 NamedTuples 序列化以正确地进行 dict :

    elif hasattr(obj, "_asdict"):
        return todict(obj._asdict())