Рекурсивно обращаться к dict через атрибуты, а также доступ к индексу?

Я хотел бы сделать что-то вроде этого:

from dotDict import dotdictify

life = {'bigBang':
           {'stars':
               {'planets': []}
           }
       }

dotdictify(life)

# This would be the regular way:
life['bigBang']['stars']['planets'] = {'earth': {'singleCellLife': {}}}
# But how can we make this work?
life.bigBang.stars.planets.earth = {'singleCellLife': {}}

#Also creating new child objects if none exist, using the following syntax:
life.bigBang.stars.planets.earth.multiCellLife = {'reptiles':{},'mammals':{}}

Мои мотивы - улучшить сжатие кода и, если возможно, использовать аналогичный синтаксис Javascript для доступа к объектам JSON для эффективной кросс-платформенной разработки. (Я также использую Py2JS и тому подобное.)

Ответ 1

Здесь один из способов создать такой опыт:

class DotDictify(dict):
    MARKER = object()

    def __init__(self, value=None):
        if value is None:
            pass
        elif isinstance(value, dict):
            for key in value:
                self.__setitem__(key, value[key])
        else:
            raise TypeError('expected dict')

    def __setitem__(self, key, value):
        if isinstance(value, dict) and not isinstance(value, DotDictify):
            value = DotDictify(value)
        super(DotDictify, self).__setitem__(key, value)

    def __getitem__(self, key):
        found = self.get(key, DotDictify.MARKER)
        if found is DotDictify.MARKER:
            found = DotDictify()
            super(DotDictify, self).__setitem__(key, found)
        return found

    __setattr__, __getattr__ = __setitem__, __getitem__


if __name__ == '__main__':

    life = {'bigBang':
               {'stars':
                   {'planets': {}  # Value changed from []
                   }
               }
           }

    life = DotDictify(life)
    print(life.bigBang.stars.planets)  # -> []
    life.bigBang.stars.planets.earth = {'singleCellLife' : {}}
    print(life.bigBang.stars.planets)  # -> {'earth': {'singleCellLife': {}}}

Ответ 2

Ниже приведена еще одна реализация словаря вложенных атрибутов (вдохновленного ответом Курта Хагенлокера, урезанного до необходимого):

class AttrDict(dict):
    """ Nested Attribute Dictionary

    A class to convert a nested Dictionary into an object with key-values
    accessibly using attribute notation (AttrDict.attribute) in addition to
    key notation (Dict["key"]). This class recursively sets Dicts to objects,
    allowing you to recurse down nested dicts (like: AttrDict.attr.attr)
    """

    def __init__(self, mapping=None):
        super(AttrDict, self).__init__()
        if mapping is not None:
            for key, value in mapping.items():
                self.__setitem__(key, value)

    def __setitem__(self, key, value):
        if isinstance(value, dict):
            value = AttrDict(value)
        super(AttrDict, self).__setitem__(key, value)
        self.__dict__[key] = value  # for code completion in editors

    def __getattr__(self, item):
        try:
            return self.__getitem__(item)
        except KeyError:
            raise AttributeError(item)

    __setattr__ = __setitem__

Это работает в Python 2 и 3:

life = AttrDict({'bigBang': {'stars': {'planets': {}}}})
life['bigBang']['stars']['planets'] = {'earth': {'singleCellLife': {}}}
life.bigBang.stars.planets.earth.multiCellLife = {'reptiles': {}, 'mammals': {}}
print(life.bigBang.stars.planets.earth)
# -> {'singleCellLife': {}, 'multiCellLife': {'mammals': {}, 'reptiles': {}}}

Преобразование KeyError в AttributeError в __getattr__ требуется в Python3, так что hasattr работает также в случае, если атрибут не найден:

hasattr(life, 'parallelUniverse')
# --> False

Ответ 3

Вот еще одно решение:

from typing import Dict, Any

class PropertyTree: pass

def dict_to_prop_tree(yaml_config: Dict[str, Any]) -> PropertyTree:
    tree = PropertyTree()
    for key, value in yaml_config.items():
        if type(value) == dict:
            setattr(tree, key, dict_to_obj_tree(value))
        elif type(value) == list:
            setattr(tree, key, [dict_to_obj_tree(v) for v in value])
        else:
            setattr(tree, key, value)
    return tree

Затем в консоли Python:

d={'a': 1, 'b': 2, 'c': {'d': 4, 'e': 5, 'f': {'g': 6}, 'h': {}, 'j': 7}}
tree=dict_to_prop_tree(d)
tree.a
tree.c.f.g

печатает правильные значения

Ответ 4

class AccessMode(dict):
    def __init__(self, mapping = None):
        super(AccessMode, self).__init__()
        if mapping is not None:
            if isinstance(mapping, dict):
                for k, v in mapping.items():
                    if isinstance(v, dict):
                        v = AccessMode(v)
                        self.__setitem__(k, v)
                    else:
                        print "TypeError: Input must be a 'dict' type.\n"

    def __setitem__(self, k, v):
        super(AccessMode, self).__setitem__(k, v)

    def __getitem__(self, k):
        return super(AccessMode,self).__getitem__(k)

    def __missing__(self, k):
        tmp = AccessMode()
        self[k] = tmp
        return tmp 

    __setattr__, __getattr__ = __setitem__, __getitem__