Вопрос по python – Рекурсивно конвертировать граф объектов Python в словарь

27

Я пытаюсь преобразовать данные из графа простого объекта в словарь. Мне не нужна информация о типе или методы, и мне не нужно иметь возможность снова преобразовывать его в объект.

я нашелэтот вопрос о создании словаря из полей объекта, но он не делает это рекурсивно.

Будучи относительно новым для python, я обеспокоен тем, что моё решение может быть некрасивым, или непитонным, или сломанным каким-то неясным способом, или просто старой NIH.

Моя первая попытка, казалось, работала, пока я не попробовал это со списками и словарями, и казалось, что проще просто проверить, имеет ли переданный объект внутренний словарь, а если нет, просто обработать его как значение (вместо того, чтобы делать всю эту проверку экземпляра) ). Мои предыдущие попытки также не повторялись в списках объектов:

<code>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
</code>

Кажется, это работает лучше и не требует исключений, но, опять же, я все еще не уверен, есть ли здесь случаи, когда я не знаю, где он падает.

Любые предложения будут высоко ценится.

особый случай может быть, когда объект имеетslotsОтредактированный ответ Anurag Uniyal
да, в основном это стиль, но иногда исключение может просто кодировать, например. когда внутренняя часть большинства функций сообщает об исключениях, и иногда это может ускорить процесс, например, если 99,99% объектов имеют dict в этом случае вместо проверки 99,99% раз на dict, проверьте исключение 0,01% времени Anurag Uniyal
точка зрения принята, но исключение - это что-то вроде священной войны, и я склоняюсь к тому, чтобы предпочесть, чтобы их никогда не бросали, если что-то действительно исключительное, а не ожидаемый ход программы. каждый на свое усмотрение :) Shabbyrobe
в python не так уж и плохо использовать исключения, и иногда это может упростить кодирование, питонский способ - EAFP (проще просить прощения, чем разрешения) Anurag Uniyal

Ваш Ответ

8   ответов
6

Я не знаю, какова цель проверки базовой строки или объекта? также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': {{apos; a1 ': 1}} могут быть некоторые другие случаи для рассмотрения, но это может быть хорошим началом

special cases если объект использует слоты, вы не сможете получитьdict например

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

исправить в слотах может быть использование dir () вместо прямого использованияdict

Похоже, что это будет сложнее, потому что то, что происходит с объектом, который предоставляет возможность итерировать атрибут списка, который вы уже поместили в dict, может быть общим решением не возможно.
Спасибо за помощь и вдохновение. Я только что понял, что он не обрабатывает списки объектов, поэтому я обновил свою версию, чтобы проверитьiter, Не уверен, что это хорошая идея. Shabbyrobe
37

Объединение моих собственных попыток и подсказок, полученных из ответов Анурага Юниала и Леннарта Регебро, работает лучше всего для меня:

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
Спасибо, чтоmostly работал. Несколько предостережений: в Python 3.5iteritems() должно бытьitems(), а также[todict(v, classkey) for v in obj] (строка 10) пытается перебирать символы в строках, исправляя это с помощью:elif hasattr(obj, "__iter__") and not isinstance(obj, str).
элегантное решение!
красиво сделано единственная реализация, которая работает так, как я хотел, пока.
круто, просто вернул мне часы моей жизни ... спасибо!
1

Медленный, но простой способ сделать это - использоватьjsonpickle преобразовать объект в строку JSON, а затемjson.loads преобразовать его обратно в словарь Python:

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

7

Однострочный код для рекурсивного преобразования объекта в json

import json
print(json.dumps(a, default=lambda o: getattr(o, '__dict__', str(o))))
1

Я понимаю, что этот ответ слишком запоздал на несколько лет, но я подумал, что стоит поделиться им, поскольку это совместимая с 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))
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
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:
        resu,lt = {}
        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и я уверен, что есть еще много случаев, которые он не охватывает и не может быть покрыт. :)

Это, к сожалению, происходит сбой с «subobj не определено».
0

Небольшое обновление ответа Шаббироба, чтобы он работал на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

Похожие вопросы