Search code examples
pythondictionaryoopsyntaxgetattr

Recursively access dict via attributes as well as index access?


I'd like to be able to do something like this:

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

My motivations are to improve the succinctness of the code, and if possible use similar syntax to Javascript for accessing JSON objects for efficient cross platform development. (I also use Py2JS and similar.)


Solution

  • Here's one way to create that kind of experience:

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