¿Acceso recursivo dict a través de atributos, así como el acceso al índice?

Me gustaría poder hacer algo como esto:

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':{}} 

Mis motivaciones son mejorar el carácter sucinto del código y, si es posible, usar una syntax similar a Javascript para acceder a objetos JSON para un desarrollo eficiente de múltiples plataformas. (También uso Py2JS y similares.)

Aquí hay una forma de crear ese tipo de experiencia:

 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': {}}} 

Debajo de otra implementación de un diccionario de atributos nested (inspirado en la respuesta de Curt Hagenlocher, simplificado a lo esencial):

 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__ 

Esto funciona tanto en Python 2 como en 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': {}}} 

La conversión de KeyError en AttributeError en __getattr__ es necesaria en Python3, por lo que hasattr también funciona en caso de que no se encuentre el atributo:

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

Aquí hay otra solución:

 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 

Luego en la consola de 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.cfg 

imprime los valores correctos

 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__