Рекурсивно преобразовать граф объекта 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
Кажется, что он работает лучше и не требует исключений, но я еще не уверен, есть ли случаи, когда я не знаю, где он падает.
Любые предложения будут высоко оценены.
Ответы
Ответ 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
Ответ 2
Одна строка кода для рекурсивного преобразования объекта в JSON.
import json
print(json.dumps(a, default=lambda o: getattr(o, '__dict__', str(o))))
Ответ 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
Ответ 4
В 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 an iterable, return all the contents
return [todict(x) for x in iter(obj)]
except TypeError:
pass
try: # If it a dictionary, recurse over it:
result = {}
for key in obj:
result[key] = todict(obj)
return result
except TypeError:
pass
# It neither a list nor a dict, so it 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, и я уверен, что есть еще много случаев, которые он не покрывает и может не быть закрываемым.:)
Ответ 5
Медленным, но простым способом сделать это - использовать jsonpickle
для преобразования объекта в строку JSON, а затем json.loads
, чтобы преобразовать его обратно в словарь python:
dict = json.loads(jsonpickle.encode( obj, unpicklable=False ))
Ответ 6
Я понимаю, что этот ответ за несколько лет слишком поздно, но я подумал, что это может стоить того, что он совместим с 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))
Ответ 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