递归地将python对象图转换为字典
Posted
技术标签:
【中文标题】递归地将python对象图转换为字典【英文标题】:Recursively convert python object graph to dictionary 【发布时间】:2010-11-05 09:41:34 【问题描述】:我正在尝试将数据从简单的对象图转换为字典。我不需要类型信息或方法,也不需要能够再次将其转换回对象。
我找到了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
这似乎工作得更好,不需要例外,但我仍然不确定这里是否有我不知道它落在哪里的案例。
任何建议将不胜感激。
【问题讨论】:
在python中使用异常并不是那么糟糕,有时它可以简化编码,这是一种pythonic方式-EAFP(Easier to Ask Forgiveness than Permission) 特殊情况可能是当对象有 slots,已编辑答案 点了,但例外的事情有点像一场圣战,我倾向于永远不要抛出它们,除非有什么真正的例外,而不是预期的程序流程。每个人都有自己的那个:) 是的,主要是风格,但有时异常可以真正简单地编码,例如当内部大多数功能通过异常进行通信时,有时它可以加快速度,例如如果在这种情况下 99.99% 的对象有 dict,而不是检查 99.99% 的时间,检查异常 0.01% 的时间 【参考方案1】:我自己的尝试和从 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
【讨论】:
做得很好。到目前为止,唯一能按照我的意愿工作的实现。 谢谢,大部分都有效。几个注意事项:在 Python 3.5 中,iteritems()
应该是 items()
,而 [todict(v, classkey) for v in obj]
(第 10 行)尝试迭代字符串中的字符,使用以下方法修复:elif hasattr(obj, "__iter__") and not isinstance(obj, str)
。
这会将我的字符串值转换为无。一个小而重要的错过。我通过捕获字符串类型的对象并在这些情况下返回 str(obj) 解决了这个问题。
尝试使用嵌套对象转换复杂对象,我收到此错误。 RecursionError: maximum recursion depth exceeded while calling a Python object
和回溯指向这一行 data = dict([(key, todict(value, classkey))
。知道这可能是什么吗?【参考方案2】:
将对象递归转换为 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))
【讨论】:
请问有没有这么简洁的从JSON加载对象的方式? 你可以这样做 obj.__dict__ = 'key': 'value' 从json加载对象,看这个:***.com/questions/6578986/… 这个解决方案对我不起作用。得到一个 ValueError:检测到循环引用。由于接受的解决方案有效,因此没有进一步调查。 可能是因为其中一个对象可能有对父对象的引用。【参考方案3】:我不知道检查 basestring 或 object 的目的是什么? dict 也不会包含任何可调用对象,除非您有指向此类可调用对象的属性,但在这种情况下,那不是对象的一部分吗?
所以不是检查各种类型和值,而是让 todict 转换对象,如果它引发异常,则使用原始值。
todict 只会在 obj 没有 dict 时引发异常 例如
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
【讨论】:
感谢您的帮助和启发。我刚刚意识到它不处理对象列表,所以我更新了我的版本来测试 iter。不过不确定这是否是个好主意。 看起来会变得更棘手,因为对于提供迭代器来迭代您已经放入 dict 的列表属性的对象会发生什么情况,可能是不可能的通用解决方案。【参考方案4】:一种缓慢但简单的方法是使用jsonpickle
将对象转换为JSON 字符串,然后使用json.loads
将其转换回python 字典:
dict = json.loads(jsonpickle.encode( obj, unpicklable=False ))
【讨论】:
【参考方案5】:我意识到这个答案已经晚了几年,但我认为它可能值得分享,因为它是@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))
【讨论】:
【参考方案6】:在 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 时,这仍然不涵盖这种情况,而且我敢肯定还有更多的情况没有涵盖并且可能无法涵盖。 :)
【讨论】:
不幸的是,这会因“未定义子对象”而崩溃。【参考方案7】:对 Shabbyrobe 的回答稍作更新,使其适用于 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
【讨论】:
【参考方案8】: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
【讨论】:
【参考方案9】:查看了所有解决方案,@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
【讨论】:
【参考方案10】:不需要自定义实现。可以使用jsons库。
import jsons
object_dict = jsons.dump(object_instance)
【讨论】:
【参考方案11】:我会评论接受的答案,但我的代表不够高......
接受的答案很好,但在 if
之后添加另一个 elif
以支持 NamedTuples 序列化以正确地进行 dict:
elif hasattr(obj, "_asdict"):
return todict(obj._asdict())
【讨论】:
【参考方案12】:嗯。添加了将深度限制为@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
【讨论】:
如果对象没有正确执行 dict 方法,那么您需要使用 dir()以上是关于递归地将python对象图转换为字典的主要内容,如果未能解决你的问题,请参考以下文章