Преобразовать вложенный Python dict в объект?


540

Я ищу элегантный способ получить данные, используя доступ к атрибутам на dict с некоторыми вложенными dicts и списками (то есть синтаксис объекта в стиле javascript).

Например:

>>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}

Должно быть доступно следующим образом:

>>> x = dict2obj(d)
>>> x.a
1
>>> x.b.c
2
>>> x.d[1].foo
bar

Я думаю, это невозможно без рекурсии, но что было бы хорошим способом получить стиль объекта для диктов?


6
Недавно я пытался сделать что-то похожее, но повторяющийся словарный ключ («от» - ключевое слово Python) не позволил мне пройти через это. Потому что, как только вы попытаетесь использовать «x.from» для доступа к этому атрибуту, вы получите синтаксическую ошибку.
Доуи Стросс

3
это действительно проблема, но я могу отказаться от "from", чтобы упростить жизнь при доступе к большим конструкциям dict :) ввод x ['a'] ['d'] [1] ['foo'] действительно раздражает, так что xad [1] .foo правила. если вам нужно от, вы можете получить к нему доступ через getattr (x, 'from') или использовать вместо него _from в качестве атрибута.
Marc

5
from_а не в _fromсоответствии с ОПТОСОЗ 8 .
Кос

1
Вы можете использовать getattr(x, 'from')вместо переименования атрибута.
Джордж В. Рейли

3
Кажется, что большинство этих «решений» не работают (даже принятое, не допускает вложенность d1.b.c), я думаю, ясно, что вы должны использовать что-то из библиотеки, например namedtuple из коллекций , как предполагает этот ответ , .. .
Энди Хайден

Ответы:


662

Обновление: в Python 2.6 и более поздних версиях подумайте, соответствует ли namedtupleструктура данных вашим потребностям:

>>> from collections import namedtuple
>>> MyStruct = namedtuple('MyStruct', 'a b d')
>>> s = MyStruct(a=1, b={'c': 2}, d=['hi'])
>>> s
MyStruct(a=1, b={'c': 2}, d=['hi'])
>>> s.a
1
>>> s.b
{'c': 2}
>>> s.c
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyStruct' object has no attribute 'c'
>>> s.d
['hi']

Альтернатива (оригинальное содержание ответа):

class Struct:
    def __init__(self, **entries):
        self.__dict__.update(entries)

Затем вы можете использовать:

>>> args = {'a': 1, 'b': 2}
>>> s = Struct(**args)
>>> s
<__main__.Struct instance at 0x01D6A738>
>>> s.a
1
>>> s.b
2

19
То же самое здесь - это особенно полезно для восстановления объектов Python из баз данных, ориентированных на документы, таких как MongoDB.
mikemaccana

13
Чтобы получить более красивую печать, добавьте: def repr __ (self): вернуть '<% s>'% str ('\ n' .join ('% s:% s'% (k, repr (v)) для (k, v). ) в себе .__ dict .iteritems ()))
шесть8

16
Будет ли это работать с вложенными словарями? и дикты, содержащие объекты и / или список и т. д. Есть ли какие-то ловушки?
Сэм Стоилинга

5
@Sam S: он не будет создавать вложенные Structs из вложенных словарей, но в целом тип значения может быть любым. Ключ ограничен тем, чтобы быть подходящим для слота объекта
Эли Бендерски

52
-1 , потому что а) она не работает для вложенной dicts, что этот вопрос четко просило, и б) в настоящее время существует та же функциональность в стандартных библиотеках argparse.Namespace(которые также определенных __eq__, __ne__, __contains__).
Вим

110
class obj(object):
    def __init__(self, d):
        for a, b in d.items():
            if isinstance(b, (list, tuple)):
               setattr(self, a, [obj(x) if isinstance(x, dict) else x for x in b])
            else:
               setattr(self, a, obj(b) if isinstance(b, dict) else b)

>>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
>>> x = obj(d)
>>> x.b.c
2
>>> x.d[1].foo
'bar'

7
Хорошо! Я бы заменил .items () на .iteritems (), однако, для меньшего объема памяти.
Эрик О Лебиго

7
Если это не требование OP, это не проблема - но учтите, что это не будет рекурсивно обрабатывать объекты в списках внутри списков.
Anon

3
Я понимаю, что это старый ответ, но в наши дни было бы лучше использовать абстрактный базовый класс вместо этой уродливой строкиif isinstance(b, (list, tuple)):
wim

4
Подсказка: имейте objнаследование класса argparse.Namespaceдля дополнительных функций, таких как читаемое строковое представление.
Серрано

2
В зависимости от случая использования, как правило , мы бы проверить , что это не строковый тип , но что она является тип последовательности
Вим

109

Удивительно, но никто не упомянул Банча . Эта библиотека предназначена исключительно для предоставления доступа к объектам dict в стиле атрибутов и выполняет именно то, что хочет OP. Демонстрация:

>>> from bunch import bunchify
>>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
>>> x = bunchify(d)
>>> x.a
1
>>> x.b.c
2
>>> x.d[1].foo
'bar'

Библиотека Python 3 доступна по адресу https://github.com/Infinidat/munch - Кредит переходит на codyzu


16
И есть совместимая с Python 3 (кажется, 2.6-3.4) ветка Bunch с именем Munch: github.com/Infinidat/munch
codyzu

Bunch - лучшее решение для всего этого, потому что он хорошо поддерживает несколько типов сериализации и поддерживается. Отлично, спасибо. Я бы хотел, чтобы версия на python3 была названа так же, потому что почему бы и нет.
Джонатан

4
Так что просто предупреждение, Bunch и Attrdict в этом отношении очень медленные. Они потребляли около 1/3 и 1/2 времени моего запроса соответственно, когда они видели частое использование в нашем приложении. Определенно не то, чтобы игнорировать. Обсудите подробнее об этом stackoverflow.com/a/31569634/364604 .
JayD3e

Хотя это позволяет объектно-подобный доступ к диктовкам, это происходит через getattr . Это затрудняет интроспекцию в интеллектуальных REPL, таких как IPython.
GDorn

Любопытно, как это сравнивается с JSONpath github.com/kennknowles/python-jsonpath-rw
MarkHu

64
x = type('new_dict', (object,), d)

затем добавьте рекурсию к этому, и все готово.

отредактируйте это так, как я бы это реализовал:

>>> d
{'a': 1, 'b': {'c': 2}, 'd': ['hi', {'foo': 'bar'}]}
>>> def obj_dic(d):
    top = type('new', (object,), d)
    seqs = tuple, list, set, frozenset
    for i, j in d.items():
        if isinstance(j, dict):
            setattr(top, i, obj_dic(j))
        elif isinstance(j, seqs):
            setattr(top, i, 
                type(j)(obj_dic(sj) if isinstance(sj, dict) else sj for sj in j))
        else:
            setattr(top, i, j)
    return top

>>> x = obj_dic(d)
>>> x.a
1
>>> x.b.c
2
>>> x.d[1].foo
'bar'

1
Почему вы создаете объекты типа, а не создаете их экземпляры? Разве это не было бы более логичным? Я имею в виду, почему бы не сделать это top_instance = top()и вернуть туда, куда вы вернетесь top?
блин

6
Nice для данных «листьев», но примеры удобно оставить из «веточки» , как xи x.bвозвращающие некрасиво<class '__main__.new'>
MarkHu

46

Есть помощник коллекции namedtuple, который может сделать это для вас:

from collections import namedtuple

d_named = namedtuple('Struct', d.keys())(*d.values())

In [7]: d_named
Out[7]: Struct(a=1, b={'c': 2}, d=['hi', {'foo': 'bar'}])

In [8]: d_named.a
Out[8]: 1

28
Это не отвечает на вопрос рекурсии для вложенных диктов.
2013 года

4
Вы не можете изменять именованные кортежи.
Макс

Можем ли мы гарантировать, что порядок списка, возвращаемого keys () и values ​​(), совпадает по порядку? Я имею в виду, если это OrderedDict, да. Но стандартный дикт? Я знаю, что «компактные» дикты CPython 3.6+ и PyPy упорядочены, но цитирую документацию: «Сохраняющий порядок аспект этой новой реализации рассматривается как деталь реализации и на нее не следует полагаться»
Хавок

39
class Struct(object):
    """Comment removed"""
    def __init__(self, data):
        for name, value in data.iteritems():
            setattr(self, name, self._wrap(value))

    def _wrap(self, value):
        if isinstance(value, (tuple, list, set, frozenset)): 
            return type(value)([self._wrap(v) for v in value])
        else:
            return Struct(value) if isinstance(value, dict) else value

Может использоваться с любой структурой последовательности / dict / value любой глубины.


4
Это должно быть ответом. Это хорошо работает для вложения. Вы можете использовать это как object_hook для json.load ().
010110110101

3
Аналогично функциональному ответу SilentGhost от 2009 года - данные конечного узла доступны, но родительский элемент / ветки отображаются как ссылки на объекты. Чтобы красиво печатать,def __repr__(self): return '{%s}' % str(', '.join("'%s': %s" % (k, repr(v)) for (k, v) in self.__dict__.iteritems()))
MarkHu

5
Пользователи Python 3.x: это просто .items()вместо .iteritems()строки 4. (Функция была переименована, но делает то же самое )
neo post modern

Работает идеально для вложенных объектов, спасибо! В качестве комментария новички, как я, для python3 iteritems () должны быть изменены на items ()
Óscar Andreu

31

Взяв то, что я чувствую, являются лучшими аспектами предыдущих примеров, вот что я придумал:

class Struct:
  '''The recursive class for building and representing objects with.'''
  def __init__(self, obj):
    for k, v in obj.iteritems():
      if isinstance(v, dict):
        setattr(self, k, Struct(v))
      else:
        setattr(self, k, v)
  def __getitem__(self, val):
    return self.__dict__[val]
  def __repr__(self):
    return '{%s}' % str(', '.join('%s : %s' % (k, repr(v)) for
      (k, v) in self.__dict__.iteritems()))

Обратите внимание, что конструктор может быть сокращен до: def __init__(self, dct): for k, v in dct.iteritems(): setattr(self, k, isinstance(v, dict) and self.__class__(v) or v) который также удаляет явный вызовStruct
George V. Reilly

2
Я бы не стал опускать свой собственный ответ, но, оглядываясь назад, я заметил, что он не переходит в типы последовательности. xd [1] .foo терпит неудачу в этом случае.
Andyvanee

2
isinstance(v, dict)проверка будет лучше, isinstance(v, collections.Mapping)так что он может обрабатывать будущее ДИКТ подобные вещи
варочные панели

30

Если ваш dict исходит из json.loads(), вы можете вместо этого превратить его в объект (а не в dict) в одну строку:

import json
from collections import namedtuple

json.loads(data, object_hook=lambda d: namedtuple('X', d.keys())(*d.values()))

Смотрите также Как преобразовать данные JSON в объект Python .


Кажется, намного медленнее, чем обычно, .loadsесли у вас есть огромный объект JSON
michaelsnowden

16

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

class Dictate(object):
    """Object view of a dict, updating the passed in dict when values are set
    or deleted. "Dictate" the contents of a dict...: """

    def __init__(self, d):
        # since __setattr__ is overridden, self.__dict = d doesn't work
        object.__setattr__(self, '_Dictate__dict', d)

    # Dictionary-like access / updates
    def __getitem__(self, name):
        value = self.__dict[name]
        if isinstance(value, dict):  # recursively view sub-dicts as objects
            value = Dictate(value)
        return value

    def __setitem__(self, name, value):
        self.__dict[name] = value
    def __delitem__(self, name):
        del self.__dict[name]

    # Object-like access / updates
    def __getattr__(self, name):
        return self[name]

    def __setattr__(self, name, value):
        self[name] = value
    def __delattr__(self, name):
        del self[name]

    def __repr__(self):
        return "%s(%r)" % (type(self).__name__, self.__dict)
    def __str__(self):
        return str(self.__dict)

Пример использования:

d = {'a': 'b', 1: 2}
dd = Dictate(d)
assert dd.a == 'b'  # Access like an object
assert dd[1] == 2  # Access like a dict
# Updates affect d
dd.c = 'd'
assert d['c'] == 'd'
del dd.a
del dd[1]
# Inner dicts are mapped
dd.e = {}
dd.e.f = 'g'
assert dd['e'].f == 'g'
assert d == {'c': 'd', 'e': {'f': 'g'}}

13
>>> def dict2obj(d):
        if isinstance(d, list):
            d = [dict2obj(x) for x in d]
        if not isinstance(d, dict):
            return d
        class C(object):
            pass
        o = C()
        for k in d:
            o.__dict__[k] = dict2obj(d[k])
        return o


>>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
>>> x = dict2obj(d)
>>> x.a
1
>>> x.b.c
2
>>> x.d[1].foo
'bar'

12

Я закончил тем, что пытался ОБА AttrDict и Букетбиблиотеки и обнаружили, что они слишком медленные для моего использования. После того, как мы с другом посмотрели на него, мы обнаружили, что основной метод написания этих библиотек приводит к агрессивной рекурсии библиотеки через вложенный объект и копированию объекта словаря. Имея это в виду, мы сделали два ключевых изменения. 1) Мы сделали атрибуты с отложенной загрузкой 2) вместо создания копий словарного объекта, мы создаем копии легкого прокси-объекта. Это окончательная реализация. Увеличение производительности при использовании этого кода невероятно. При использовании AttrDict или Bunch только эти две библиотеки потребляли 1/2 и 1/3 соответственно моего времени запроса (что !?). Этот код сократил это время практически до нуля (где-то в диапазоне 0,5 мс). Это, конечно, зависит от ваших потребностей,

class DictProxy(object):
    def __init__(self, obj):
        self.obj = obj

    def __getitem__(self, key):
        return wrap(self.obj[key])

    def __getattr__(self, key):
        try:
            return wrap(getattr(self.obj, key))
        except AttributeError:
            try:
                return self[key]
            except KeyError:
                raise AttributeError(key)

    # you probably also want to proxy important list properties along like
    # items(), iteritems() and __len__

class ListProxy(object):
    def __init__(self, obj):
        self.obj = obj

    def __getitem__(self, key):
        return wrap(self.obj[key])

    # you probably also want to proxy important list properties along like
    # __iter__ and __len__

def wrap(value):
    if isinstance(value, dict):
        return DictProxy(value)
    if isinstance(value, (tuple, list)):
        return ListProxy(value)
    return value

Смотрите оригинальную реализацию здесь по адресу https://stackoverflow.com/users/704327/michael-merickel .

Следует также отметить, что эта реализация довольно проста и не реализует все методы, которые могут вам понадобиться. Вам нужно будет написать их, как требуется, для объектов DictProxy или ListProxy.


9

x.__dict__.update(d) должно быть хорошо.


спасибо за ваш ответ, но что такое х? диктат или стандартный объект? не могли бы вы дать мне подсказку, пожалуйста.
Marc

х ваш объект. У каждого объекта есть диктат . Обновляя dict объекта, вы фактически обновляете ключевые переменные в нем.
Алекс Родригес

Смелые слова _ _ dict _ _
Алекс Родригес

15
Это не будет обрабатывать вложенные словари.
FogleBird

Не каждый объект имеет __dict__: попробуйте, object().__dict__и вы получитеAttributeError: 'object' object has no attribute '__dict__'
Уилл Мэнли

8

Вы можете использовать jsonмодуль стандартной библиотеки с пользовательским хуком объектов :

import json

class obj(object):
    def __init__(self, dict_):
        self.__dict__.update(dict_)

def dict2obj(d):
    return json.loads(json.dumps(d), object_hook=obj)

Пример использования:

>>> d = {'a': 1, 'b': {'c': 2}, 'd': ['hi', {'foo': 'bar'}]}
>>> o = dict2obj(d)
>>> o.a
1
>>> o.b.c
2
>>> o.d[0]
u'hi'
>>> o.d[1].foo
u'bar'

И это не только для чтения, как это происходит namedtuple, то есть вы можете изменять значения, а не структуру:

>>> o.b.c = 3
>>> o.b.c
3

1
Лучший ответ на сегодняшний день
Коннор

Мне нравится идея использовать механизм загрузки json для вложенных элементов. Но у нас уже есть диктат, и мне не нравится тот факт, что нужно создать из него строку, чтобы отобразить ее в объект. Я бы предпочел иметь решение, которое создает объекты непосредственно из диктата.
Сандро

1
Нужно сначала преобразовать в строку, но довольно общий, поскольку можно расширить его с помощью json.JSONEncoderи object_hook.
Сандро

6

Это должно начать ваше:

class dict2obj(object):
    def __init__(self, d):
        self.__dict__['d'] = d

    def __getattr__(self, key):
        value = self.__dict__['d'][key]
        if type(value) == type({}):
            return dict2obj(value)

        return value

d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}

x = dict2obj(d)
print x.a
print x.b.c
print x.d[1].foo

Это не работает для списков, пока. Вам придется обернуть списки в UserList и перегружать, __getitem__чтобы обернуть слова.


2
Чтобы это работало для списков, используйте if isinstance(d, list)предложение from Anon's answer.
Vinay Sajip

6

Я знаю, что здесь уже есть много ответов, и я опаздываю на вечеринку, но этот метод рекурсивно и «на месте» преобразует словарь в объектоподобную структуру ... Работает в 3.xx

def dictToObject(d):
    for k,v in d.items():
        if isinstance(v, dict):
            d[k] = dictToObject(v)
    return namedtuple('object', d.keys())(*d.values())

# Dictionary created from JSON file
d = {
    'primaryKey': 'id', 
    'metadata': 
        {
            'rows': 0, 
            'lastID': 0
        }, 
    'columns': 
        {
            'col2': {
                'dataType': 'string', 
                'name': 'addressLine1'
            }, 
            'col1': {
                'datatype': 'string', 
                'name': 'postcode'
            }, 
            'col3': {
                'dataType': 'string', 
                'name': 'addressLine2'
            }, 
            'col0': {
                'datatype': 'integer', 
                'name': 'id'
            }, 
            'col4': {
                'dataType': 'string', 
                'name': 'contactNumber'
            }
        }, 
        'secondaryKeys': {}
}

d1 = dictToObject(d)
d1.columns.col1 # == object(datatype='string', name='postcode')
d1.metadata.rows # == 0

Что вы имеете в виду «объектоподобная структура»?
MoralCode

1
Объектоподобный из-за принципа утки. Я имею в виду, что вы можете получить доступ к его свойствам, как если бы это был экземпляр класса. но это не объект в этом смысле, это именованный кортеж.
Эйдан Хэддон-Райт

5
from mock import Mock
d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
my_data = Mock(**d)

# We got
# my_data.a == 1

4

Позвольте мне объяснить решение, которое я почти использовал некоторое время назад. Но сначала причина, по которой я этого не сделал, иллюстрируется тем, что следующий код:

d = {'from': 1}
x = dict2obj(d)

print x.from

дает эту ошибку:

  File "test.py", line 20
    print x.from == 1
                ^
SyntaxError: invalid syntax

Поскольку «from» является ключевым словом Python, существуют определенные словарные ключи, которые вы не можете разрешить.


Теперь мое решение позволяет получить доступ к элементам словаря, используя их имена напрямую. Но это также позволяет использовать «словарную семантику». Вот код с примером использования:

class dict2obj(dict):
    def __init__(self, dict_):
        super(dict2obj, self).__init__(dict_)
        for key in self:
            item = self[key]
            if isinstance(item, list):
                for idx, it in enumerate(item):
                    if isinstance(it, dict):
                        item[idx] = dict2obj(it)
            elif isinstance(item, dict):
                self[key] = dict2obj(item)

    def __getattr__(self, key):
        return self[key]

d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}

x = dict2obj(d)

assert x.a == x['a'] == 1
assert x.b.c == x['b']['c'] == 2
assert x.d[1].foo == x['d'][1]['foo'] == "bar"

1
Структура класса: def init __ (self, ** записи): self .__ dict .update (записи)
Кеннет Рейтц

4

Старые вопросы и ответы, но у меня есть кое-что еще, чтобы поговорить. Кажется, никто не говорит о рекурсивных диктовках. Это мой код:

#!/usr/bin/env python

class Object( dict ):
    def __init__( self, data = None ):
        super( Object, self ).__init__()
        if data:
            self.__update( data, {} )

    def __update( self, data, did ):
        dataid = id(data)
        did[ dataid ] = self

        for k in data:
            dkid = id(data[k])
            if did.has_key(dkid):
                self[k] = did[dkid]
            elif isinstance( data[k], Object ):
                self[k] = data[k]
            elif isinstance( data[k], dict ):
                obj = Object()
                obj.__update( data[k], did )
                self[k] = obj
                obj = None
            else:
                self[k] = data[k]

    def __getattr__( self, key ):
        return self.get( key, None )

    def __setattr__( self, key, value ):
        if isinstance(value,dict):
            self[key] = Object( value )
        else:
            self[key] = value

    def update( self, *args ):
        for obj in args:
            for k in obj:
                if isinstance(obj[k],dict):
                    self[k] = Object( obj[k] )
                else:
                    self[k] = obj[k]
        return self

    def merge( self, *args ):
        for obj in args:
            for k in obj:
                if self.has_key(k):
                    if isinstance(self[k],list) and isinstance(obj[k],list):
                        self[k] += obj[k]
                    elif isinstance(self[k],list):
                        self[k].append( obj[k] )
                    elif isinstance(obj[k],list):
                        self[k] = [self[k]] + obj[k]
                    elif isinstance(self[k],Object) and isinstance(obj[k],Object):
                        self[k].merge( obj[k] )
                    elif isinstance(self[k],Object) and isinstance(obj[k],dict):
                        self[k].merge( obj[k] )
                    else:
                        self[k] = [ self[k], obj[k] ]
                else:
                    if isinstance(obj[k],dict):
                        self[k] = Object( obj[k] )
                    else:
                        self[k] = obj[k]
        return self

def test01():
    class UObject( Object ):
        pass
    obj = Object({1:2})
    d = {}
    d.update({
        "a": 1,
        "b": {
            "c": 2,
            "d": [ 3, 4, 5 ],
            "e": [ [6,7], (8,9) ],
            "self": d,
        },
        1: 10,
        "1": 11,
        "obj": obj,
    })
    x = UObject(d)


    assert x.a == x["a"] == 1
    assert x.b.c == x["b"]["c"] == 2
    assert x.b.d[0] == 3
    assert x.b.d[1] == 4
    assert x.b.e[0][0] == 6
    assert x.b.e[1][0] == 8
    assert x[1] == 10
    assert x["1"] == 11
    assert x[1] != x["1"]
    assert id(x) == id(x.b.self.b.self) == id(x.b.self)
    assert x.b.self.a == x.b.self.b.self.a == 1

    x.x = 12
    assert x.x == x["x"] == 12
    x.y = {"a":13,"b":[14,15]}
    assert x.y.a == 13
    assert x.y.b[0] == 14

def test02():
    x = Object({
        "a": {
            "b": 1,
            "c": [ 2, 3 ]
        },
        1: 6,
        2: [ 8, 9 ],
        3: 11,
    })
    y = Object({
        "a": {
            "b": 4,
            "c": [ 5 ]
        },
        1: 7,
        2: 10,
        3: [ 12 , 13 ],
    })
    z = {
        3: 14,
        2: 15,
        "a": {
            "b": 16,
            "c": 17,
        }
    }
    x.merge( y, z )
    assert 2 in x.a.c
    assert 3 in x.a.c
    assert 5 in x.a.c
    assert 1 in x.a.b
    assert 4 in x.a.b
    assert 8 in x[2]
    assert 9 in x[2]
    assert 10 in x[2]
    assert 11 in x[3]
    assert 12 in x[3]
    assert 13 in x[3]
    assert 14 in x[3]
    assert 15 in x[2]
    assert 16 in x.a.b
    assert 17 in x.a.c

if __name__ == '__main__':
    test01()
    test02()

4

Хотел загрузить мою версию этой маленькой парадигмы.

class Struct(dict):
  def __init__(self,data):
    for key, value in data.items():
      if isinstance(value, dict):
        setattr(self, key, Struct(value))
      else:   
        setattr(self, key, type(value).__init__(value))

      dict.__init__(self,data)

Он сохраняет атрибуты для типа, который импортируется в класс. Мое единственное беспокойство будет переписывать методы из словаря вашего анализа. Но в остальном кажется солидным!


Хотя это хорошая идея, это, похоже, не работает для примера OP, на самом деле, похоже, оно изменяет переданный в словаре! На самом деле, df.aдаже не работает.
Энди Хейден

4

Это тоже хорошо работает

class DObj(object):
    pass

dobj = Dobj()
dobj.__dict__ = {'a': 'aaa', 'b': 'bbb'}

print dobj.a
>>> aaa
print dobj.b
>>> bbb

3

Вот еще один способ реализовать оригинальное предложение SilentGhost:

def dict2obj(d):
  if isinstance(d, dict):
    n = {}
    for item in d:
      if isinstance(d[item], dict):
        n[item] = dict2obj(d[item])
      elif isinstance(d[item], (list, tuple)):
        n[item] = [dict2obj(elem) for elem in d[item]]
      else:
        n[item] = d[item]
    return type('obj_from_dict', (object,), n)
  else:
    return d

3

Я наткнулся на случай, когда мне нужно было рекурсивно преобразовать список диктов в список объектов, поэтому, основываясь на фрагменте Роберто, вот что сработало для меня:

def dict2obj(d):
    if isinstance(d, dict):
        n = {}
        for item in d:
            if isinstance(d[item], dict):
                n[item] = dict2obj(d[item])
            elif isinstance(d[item], (list, tuple)):
                n[item] = [dict2obj(elem) for elem in d[item]]
            else:
                n[item] = d[item]
        return type('obj_from_dict', (object,), n)
    elif isinstance(d, (list, tuple,)):
        l = []
        for item in d:
            l.append(dict2obj(item))
        return l
    else:
        return d

Обратите внимание, что любой кортеж будет преобразован в эквивалент списка по очевидным причинам.

Надеюсь, это поможет кому-то так же, как все ваши ответы для меня, ребята.


3

Как насчет просто назначения вашего dictна __dict__пустой объект?

class Object:
    """If your dict is "flat", this is a simple way to create an object from a dict

    >>> obj = Object()
    >>> obj.__dict__ = d
    >>> d.a
    1
    """
    pass

Конечно, это не работает на вашем примере с вложенным диктом, если вы не выполняете рекурсивную рекурсивную процедуру:

# For a nested dict, you need to recursively update __dict__
def dict2obj(d):
    """Convert a dict to an object

    >>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
    >>> obj = dict2obj(d)
    >>> obj.b.c
    2
    >>> obj.d
    ["hi", {'foo': "bar"}]
    """
    try:
        d = dict(d)
    except (TypeError, ValueError):
        return d
    obj = Object()
    for k, v in d.iteritems():
        obj.__dict__[k] = dict2obj(v)
    return obj

И ваш примерный элемент списка, вероятно, должен был представлять Mappingсобой список пар (ключ, значение), например:

>>> d = {'a': 1, 'b': {'c': 2}, 'd': [("hi", {'foo': "bar"})]}
>>> obj = dict2obj(d)
>>> obj.d.hi.foo
"bar"

2

Вот еще одна реализация:

class DictObj(object):
    def __init__(self, d):
        self.__dict__ = d

def dict_to_obj(d):
    if isinstance(d, (list, tuple)): return map(dict_to_obj, d)
    elif not isinstance(d, dict): return d
    return DictObj(dict((k, dict_to_obj(v)) for (k,v) in d.iteritems()))

[Edit] Пропущено немного о том, как обрабатывать дикты в списках, а не только другие. Добавлено исправление.


Обратите внимание, что установка dict в исходный словарь означает, что любые изменения атрибутов в результирующем объекте также будут влиять на словарь, который создал объект, и наоборот. Это может привести к неожиданным результатам, если словарь используется не для создания объекта.
Марк Родди

@Mark: На самом деле, новый словарь передается в DictObj каждый раз, а не просто проходит через один и тот же объект dict, поэтому на самом деле этого не произойдет. Это необходимо сделать, так как мне нужно также перевести значения в словаре, так что было бы невозможно пройти через исходный объект dict, не изменяя его самостоятельно.
Брайан

Ответов на это довольно много, принятый ответ не выглядел бы как рекурсивный или обрабатывать списки. Я прочитал их все, и этот на первый взгляд выглядел самым простым, я проверил, и это сработало. Отличный ответ.
AlwaysTraining

2
class Struct(dict):
    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        self[name] = value

    def copy(self):
        return Struct(dict.copy(self))

Применение:

points = Struct(x=1, y=2)
# Changing
points['x'] = 2
points.y = 1
# Accessing
points['x'], points.x, points.get('x') # 2 2 2
points['y'], points.y, points.get('y') # 1 1 1
# Accessing inexistent keys/attrs 
points['z'] # KeyError: z
points.z # AttributeError: z
# Copying
points_copy = points.copy()
points.x = 2
points_copy.x # 1

2

Как насчет этого:

from functools import partial
d2o=partial(type, "d2o", ())

Это может быть использовано следующим образом:

>>> o=d2o({"a" : 5, "b" : 3})
>>> print o.a
5
>>> print o.b
3

2

Я думаю, что dict состоит из числа, строки и dict достаточно времени. Поэтому я игнорирую ситуацию, когда кортежи, списки и другие типы не появляются в конечном измерении слова.

Учитывая наследование в сочетании с рекурсией, он удобно решает проблему печати, а также предоставляет два способа запроса данных, один способ редактирования данных.

Посмотрите на приведенный ниже пример, который описывает некоторую информацию о студентах:

group=["class1","class2","class3","class4",]
rank=["rank1","rank2","rank3","rank4","rank5",]
data=["name","sex","height","weight","score"]

#build a dict based on the lists above
student_dic=dict([(g,dict([(r,dict([(d,'') for d in data])) for r in rank ]))for g in group])

#this is the solution
class dic2class(dict):
    def __init__(self, dic):
        for key,val in dic.items():
            self.__dict__[key]=self[key]=dic2class(val) if isinstance(val,dict) else val


student_class=dic2class(student_dic)

#one way to edit:
student_class.class1.rank1['sex']='male'
student_class.class1.rank1['name']='Nan Xiang'

#two ways to query:
print student_class.class1.rank1
print student_class.class1['rank1']
print '-'*50
for rank in student_class.class1:
    print getattr(student_class.class1,rank)

Результаты:

{'score': '', 'sex': 'male', 'name': 'Nan Xiang', 'weight': '', 'height': ''}
{'score': '', 'sex': 'male', 'name': 'Nan Xiang', 'weight': '', 'height': ''}
--------------------------------------------------
{'score': '', 'sex': '', 'name': '', 'weight': '', 'height': ''}
{'score': '', 'sex': '', 'name': '', 'weight': '', 'height': ''}
{'score': '', 'sex': 'male', 'name': 'Nan Xiang', 'weight': '', 'height': ''}
{'score': '', 'sex': '', 'name': '', 'weight': '', 'height': ''}
{'score': '', 'sex': '', 'name': '', 'weight': '', 'height': ''}

2

Обычно вы хотите отразить иерархию диктов в вашем объекте, но не список или кортежи, которые обычно находятся на самом низком уровне. Вот как я это сделал:

class defDictToObject(object):

    def __init__(self, myDict):
        for key, value in myDict.items():
            if type(value) == dict:
                setattr(self, key, defDictToObject(value))
            else:
                setattr(self, key, value)

Итак, мы делаем:

myDict = { 'a': 1,
           'b': { 
              'b1': {'x': 1,
                    'y': 2} },
           'c': ['hi', 'bar'] 
         }

и получить:

x.b.b1.x 1

x.c ['привет', 'бар']


1

Мой словарь имеет этот формат:

addr_bk = {
    'person': [
        {'name': 'Andrew', 'id': 123, 'email': 'andrew@mailserver.com',
         'phone': [{'type': 2, 'number': '633311122'},
                   {'type': 0, 'number': '97788665'}]
        },
        {'name': 'Tom', 'id': 456,
         'phone': [{'type': 0, 'number': '91122334'}]}, 
        {'name': 'Jack', 'id': 7788, 'email': 'jack@gmail.com'}
    ]
}

Как видно, у меня есть вложенные словари и список диктов . Это связано с тем, что addr_bk был декодирован из данных буфера протокола, которые были преобразованы в python dict с использованием lwpb.codec. Есть необязательное поле (например, email =>, где ключ может быть недоступен) и повторяемое поле (например, phone =>, преобразованное в список dict).

Я перепробовал все предложенные выше решения. Некоторые плохо обрабатывают вложенные словари. Другие не могут легко распечатать детали объекта.

Только решение, dict2obj (dict) от Dawie Strauss, работает лучше всего.

Я немного улучшил его, когда ключ не может быть найден:

# Work the best, with nested dictionaries & lists! :)
# Able to print out all items.
class dict2obj_new(dict):
    def __init__(self, dict_):
        super(dict2obj_new, self).__init__(dict_)
        for key in self:
            item = self[key]
            if isinstance(item, list):
                for idx, it in enumerate(item):
                    if isinstance(it, dict):
                        item[idx] = dict2obj_new(it)
            elif isinstance(item, dict):
                self[key] = dict2obj_new(item)

    def __getattr__(self, key):
        # Enhanced to handle key not found.
        if self.has_key(key):
            return self[key]
        else:
            return None

Затем я проверил это с:

# Testing...
ab = dict2obj_new(addr_bk)

for person in ab.person:
  print "Person ID:", person.id
  print "  Name:", person.name
  # Check if optional field is available before printing.
  if person.email:
    print "  E-mail address:", person.email

  # Check if optional field is available before printing.
  if person.phone:
    for phone_number in person.phone:
      if phone_number.type == codec.enums.PhoneType.MOBILE:
        print "  Mobile phone #:",
      elif phone_number.type == codec.enums.PhoneType.HOME:
        print "  Home phone #:",
      else:
        print "  Work phone #:",
      print phone_number.number

1

Основываясь на моем ответе на « python: Как динамически добавить свойство в класс? »:

class data(object):
    def __init__(self,*args,**argd):
        self.__dict__.update(dict(*args,**argd))

def makedata(d):
    d2 = {}
    for n in d:
        d2[n] = trydata(d[n])
    return data(d2)

def trydata(o):
    if isinstance(o,dict):
        return makedata(o)
    elif isinstance(o,list):
        return [trydata(i) for i in o]
    else:
        return o

Вы вызываете makedataсловарь, который хотите преобразовать, или, может быть, в trydataзависимости от того, что вы ожидаете в качестве ввода, и он выплевывает объект данных.

Ноты:

  • Вы можете добавить elifs, trydataесли вам нужно больше функциональности.
  • Очевидно, это не сработает, если вы хотите x.a = {}или подобное.
  • Если вы хотите версию только для чтения, используйте данные класса из исходного ответа .
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.