我正在尝试将数据从简单的对象图转换为字典。我不需要类型信息或方法,我不需要能够再次将它转换回对象。
我找到了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
这似乎工作得更好,不需要例外,但我仍然不确定这里是否有案例我不知道它在哪里落下。
我们非常感谢任何建议。
答案 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())