45

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

我发现了这个关于从对象的字段创建字典的问题,但它不会递归地执行它。

对 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

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

任何建议将不胜感激。

4

13 回答 13

56

我自己的尝试和来自 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
于 2009-07-13T07:06:27.397 回答
31

将对象递归转换为 JSON 的一行代码。

import json

def get_json(obj):
  return json.loads(
    json.dumps(obj, default=lambda o: getattr(o, '__dict__', str(o)))
  )

obj = SomeClass()
print("Json = ", get_json(obj))
于 2018-02-27T08:08:01.137 回答
8

我不知道检查 basestring 或 object 的目的是什么?除非您具有指向此类可调用对象的属性,否则dict也不包含任何可调用对象,但在这种情况下,那不是对象的一部分吗?

因此,不要检查各种类型和值,而是让 todict 转换对象,如果它引发异常,则使用原始值。

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

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

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

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

于 2009-06-24T04:41:47.760 回答
3

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

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

于 2013-06-07T22:12:37.850 回答
3

我意识到这个答案已经晚了几年,但我认为它可能值得分享,因为它是@Shabbyrobe 对原始解决方案的 Python 3.3+ 兼容修改,通常对我来说效果很好:

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))
于 2014-03-27T06:24:19.777 回答
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时,这仍然不涵盖这种情况,而且我敢肯定还有更多的情况它没有涵盖并且可能无法涵盖。:)

于 2009-06-24T05:46:05.600 回答
0

对 Shabbyrobe 的回答进行一点更新,使其适用于namedtuples:

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
于 2017-03-06T14:04:57.970 回答
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
于 2018-09-14T17:57:17.513 回答
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
于 2020-01-10T09:00:21.750 回答
0

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

import jsons

object_dict = jsons.dump(object_instance)
于 2021-03-18T11:29:04.127 回答
0

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

    elif hasattr(obj, "_asdict"):
        return todict(obj._asdict())
于 2021-03-31T05:35:48.553 回答
0

好。添加了将深度限制为@Shabbyrobe 答案的功能。认为循环返回的对象可能是值得的。

def todict(obj, limit=sys.getrecursionlimit(), classkey=None):
        if isinstance(obj, dict):
            if limit>=1:
                data = {}
                for (k, v) in obj.items():
                    data[k] = todict(v, limit-1,classkey)
                return data
            else:
                return 'class:'+obj.__class__.__name__
        elif hasattr(obj, "_ast"):
            return todict(obj._ast(), limit-1) if limit>=1 else {'class:'+obj.__class__.__name__}
        elif hasattr(obj, "__iter__") and not isinstance(obj, str):
            return [todict(v, limit-1, classkey) for v in obj] if limit>=1 else {'class:'+obj.__class__.__name__}
        elif hasattr(obj, "__dict__"):
            if limit>=1:
                data = dict([(key, todict(value, limit-1, 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 'class:'+obj.__class__.__name__
        else:
            return obj
于 2021-12-19T08:31:45.200 回答
0

谢谢@AnuragUniyal!你让我今天一整天都感觉很好!这是对我有用的代码变体:

# noinspection PyProtectedMember
def object_to_dict(obj):
    data = {}
    if getattr(obj, '__dict__', None):
        for key, value in obj.__dict__.items():
            try:
                data[key] = object_to_dict(value)
            except AttributeError:
                data[key] = value
        return data
    else:
        return obj
于 2022-03-06T00:03:08.333 回答