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

Я использую пакет, который возвращает вложенный словарь. Неудобно обращаться к этому возвращаемому объекту в моих методах класса с синтаксисом словаря, когда все остальное находится в синтаксисе объекта. Поиски привели меня к пакетам связка/необуч, которые, кажется, достигают того, что мне нужно. Я также видел предложения namedtuples, но они нелегко поддерживают вложенные атрибуты, и большинство решений полагаются на использование словарей в namedtuple для вложения.

Что было бы более естественным способом достижения этого?

data = {'a': 'aval', 'b': {'b1':{'b2a':{'b3a':'b3aval','b3b':'b3bval'},'b2b':'b2bval'}} }

print(data['b']['b1']['b2a']['b3b'])  # dictionary access
# print(data.b.b1.b2a.b3b)  # desired access

import neobunch
data1 = neobunch.bunchify(data)
print(data1.b.b1.b2a.b3b)

person Tim B    schedule 26.06.2016    source источник


Ответы (6)


Следующий класс позволит вам делать то, что вы хотите (работает в Python 2 и 3):

class AttrDict(dict):
    """ Dictionary subclass whose entries can be accessed by attributes (as well
        as normally).

    >>> obj = AttrDict()
    >>> obj['test'] = 'hi'
    >>> print obj.test
    hi
    >>> del obj.test
    >>> obj.test = 'bye'
    >>> print obj['test']
    bye
    >>> print len(obj)
    1
    >>> obj.clear()
    >>> print len(obj)
    0
    """
    def __init__(self, *args, **kwargs):
        super(AttrDict, self).__init__(*args, **kwargs)
        self.__dict__ = self

    @classmethod
    def from_nested_dicts(cls, data):
        """ Construct nested AttrDicts from nested dictionaries. """
        if not isinstance(data, dict):
            return data
        else:
            return cls({key: cls.from_nested_dicts(data[key]) for key in data})


if __name__ == '__main__':

    data = {
        "a": "aval",
        "b": {
            "b1": {
                "b2b": "b2bval",
                "b2a": {
                    "b3a": "b3aval",
                    "b3b": "b3bval"
                }
            }
        }
    }

    data1 = AttrDict.from_nested_dicts(data)
    print(data1.b.b1.b2a.b3b)  # -> b3bval

person martineau    schedule 26.06.2016
comment
Не могли бы вы уточнить, почему/как работает код? Спасибо! - person ; 08.01.2017
comment
@BartKleijngeld: Какие части вы не понимаете? - person martineau; 08.01.2017
comment
Я не понимаю, как ключи словаря каким-то образом становятся свойствами объекта data1. Я чувствую, что упускаю что-то очень простое, но был бы признателен, если бы вы могли объяснить мне эту часть :). - person ; 08.01.2017
comment
@BartKleijngeld: Конечно... это довольно странный зверь. AttrDict — это подкласс словаря, который использует самого себя в качестве класса __dict__. В последнем обычно хранятся атрибуты экземпляра, на которые обычно ссылаются с помощью нотации . (точка), например obj.attr, но, поскольку это словарь, вы все равно можете получить к ним доступ с помощью обычной нотации [], например obj['attr']. Это идея, заимствованная из Javascript. В ActiveState есть другой рецепт, и я видел в нескольких других местах. , слишком. - person martineau; 08.01.2017

Опираясь на отличный ответ @martineau, вы можете заставить класс AttrDict работать с вложенными словарями без явного вызова функции from_nested_dict() :

class AttrDict(dict):
""" Dictionary subclass whose entries can be accessed by attributes
    (as well as normally).
"""
def __init__(self, *args, **kwargs):
    def from_nested_dict(data):
        """ Construct nested AttrDicts from nested dictionaries. """
        if not isinstance(data, dict):
            return data
        else:
            return AttrDict({key: from_nested_dict(data[key])
                                for key in data})

    super(AttrDict, self).__init__(*args, **kwargs)
    self.__dict__ = self

    for key in self.keys():
        self[key] = from_nested_dict(self[key])
person Saravana Kumar    schedule 30.01.2020
comment
блестящее дополнение! - person naturesenshi; 26.03.2021

Попробуйте Dotsi или EasyDict. Оба они поддерживают точечную нотацию для вложенных словарей.

>>> import dotsi
>>> data = dotsi.fy({'a': 'aval', 'b': {'b1':{'b2a':{'b3a':'b3aval','b3b':'b3bval'},'b2b':'b2bval'}} })
>>> print(data.b.b1.b2a.b3b)
b3bval
>>> 

В дополнение к словарям внутри словарей Dotsi также поддерживает словари внутри списков внутри словарей.
Примечание. Я автор Dotsi.

person Sumukh Barve    schedule 10.11.2020

json.loads имеет интересный параметр под названием object_hook, который можно использовать, если все значения словаря являются сериализуемыми JSON, т.е.

import json
from types import SimpleNamespace

data = {'a': 'aval', 'b': {'b1':{'b2a':{'b3a':'b3aval','b3b':'b3bval'},'b2b':'b2bval'}}}
data1= json.loads(
    json.dumps(data), object_hook=lambda d: SimpleNamespace(**d)
)
print(data1.b.b1.b2a.b3b)  # -> b3bval

Если Гвидо слушает, я думаю, что SimpleNamespace следует принять параметр recursive, так что вы можете просто сделать data1 = SimpleNamespace(recursive=True, **data).

person pandichef    schedule 13.08.2020
comment
Вероятно, это немного неэффективно, но на данный момент это самое простое решение, которое я смог найти, оно не зависит от сторонних пакетов. - person luator; 13.07.2021

Можно использовать простой класс, построенный на базовом объекте:

class afoo1(object):
    def __init__(self, kwargs):
        for name in kwargs:
            val = kwargs[name]
            if isinstance(val, dict):
                val = afoo1(val)
            setattr(self,name,val)

Я заимствую определение argparse.Namespace, измененное для обеспечения вложенности.

Он будет использоваться как

In [172]: dd={'a':'aval','b':{'b1':'bval'}}

In [173]: f=afoo1(dd)

In [174]: f
Out[174]: <__main__.afoo1 at 0xb3808ccc>

In [175]: f.a
Out[175]: 'aval'

In [176]: f.b
Out[176]: <__main__.afoo1 at 0xb380802c>

In [177]: f.b.b1
Out[177]: 'bval'

Его также можно было определить с помощью **kwargs (вместе с *args). Также может подойти определение __repr__.

Как и в случае с другими простыми объектами, атрибуты могут быть добавлены, например. f.c = f (рекурсивное определение). vars(f) возвращает словарь, но не выполняет никакого рекурсивного преобразования).

person hpaulj    schedule 26.06.2016
comment
Хотя в вашем классе нет ничего плохого с точки зрения функциональности, он мог бы иметь лучший стиль. Я бы не стал использовать kwargs для аргумента обычного словаря, так как это имя параметра обычно используется для аргументов ключевого слова, которое вообще не применяется в этом контексте. Я бы также использовал for name, value in kwargs.items() (или .iteritems() в Python 2), вместо того чтобы перебирать ключи и искать значение в следующей строке. - person Blckknght; 26.06.2016
comment
Да, хотя ошибки являются результатом неполного редактирования Namespace оригинала. - person hpaulj; 26.06.2016

Как насчет использования метода __setattr__?

>>> class AttrDict(dict):
...     def __getattr__(self, name):
...         if name in self:
...             return self[name]
... 
...     def __setattr__(self, name, value):
...         self[name] = self.from_nested_dict(value)
... 
...     def __delattr__(self, name):
...         if name in self:
...             del self[name]
... 
...     @staticmethod
...     def from_nested_dict(data):
...         """ Construct nested AttrDicts from nested dictionaries. """
...         if not isinstance(data, dict):
...             return data
...         else:
...             return AttrDict({key: AttrDict.from_nested_dict(data[key])
...                                 for key in data})
...         

>>> ad = AttrDict()
>>> ad
{}

>>> data = {'a': 'aval', 'b': {'b1':{'b2a':{'b3a':'b3aval','b3b':'b3bval'},'b2b':'b2bval'}} }

>>> ad.data = data
>>> ad.data
{'a': 'aval', 'b': {'b1': {'b2a': {'b3a': 'b3aval', 'b3b': 'b3bval'}, 'b2b': 'b2bval'}}}

>>> print(ad.data.b.b1.b2a.b3b)
    b3bval
person LuckyDams    schedule 23.03.2019