Recursive DotDict
I've been slightly unhappy with all the different answers I have found to this problem. My goals in my implementation were: 1) Don't create more new object attributes than necessary. 2) Don't allow overwriting access to built-in attributes. 3) The class converts added items to maintain consistency.
class attrdict(dict):
"""
Attribute Dictionary.
Enables getting/setting/deleting dictionary keys via attributes.
Getting/deleting a non-existent key via attribute raises `AttributeError`.
Objects are passed to `__convert` before `dict.__setitem__` is called.
This class rebinds `__setattr__` to call `dict.__setitem__`. Attributes
will not be set on the object, but will be added as keys to the dictionary.
This prevents overwriting access to built-in attributes. Since we defined
`__getattr__` but left `__getattribute__` alone, built-in attributes will
be returned before `__getattr__` is called. Be careful::
>>> a = attrdict()
>>> a['key'] = 'value'
>>> a.key
'value'
>>> a['keys'] = 'oops'
>>> a.keys
<built-in method keys of attrdict object at 0xabcdef123456>
Use `'key' in a`, not `hasattr(a, 'key')`, as a consequence of the above.
"""
def __init__(self, *args, **kwargs):
# We trust the dict to init itself better than we can.
dict.__init__(self, *args, **kwargs)
# Because of that, we do duplicate work, but it's worth it.
for k, v in self.iteritems():
self.__setitem__(k, v)
def __getattr__(self, k):
try:
return dict.__getitem__(self, k)
except KeyError:
# Maintain consistent syntactical behaviour.
raise AttributeError(
"'attrdict' object has no attribute '" + str(k) + "'"
)
def __setitem__(self, k, v):
dict.__setitem__(self, k, attrdict.__convert(v))
__setattr__ = __setitem__
def __delattr__(self, k):
try:
dict.__delitem__(self, k)
except KeyError:
raise AttributeError(
"'attrdict' object has no attribute '" + str(k) + "'"
)
@staticmethod
def __convert(o):
"""
Recursively convert `dict` objects in `dict`, `list`, `set`, and
`tuple` objects to `attrdict` objects.
"""
if isinstance(o, dict):
o = attrdict(o)
elif isinstance(o, list):
o = list(attrdict.__convert(v) for v in o)
elif isinstance(o, set):
o = set(attrdict.__convert(v) for v in o)
elif isinstance(o, tuple):
o = tuple(attrdict.__convert(v) for v in o)
return o
I don't see where you are copying the values in the constructor. Here DotDict is always empty because of that. When I added the key assignment, it worked:
class DotDict(dict):
"""
a dictionary that supports dot notation
as well as dictionary access notation
usage: d = DotDict() or d = DotDict({'val1':'first'})
set attributes: d.val2 = 'second' or d['val2'] = 'second'
get attributes: d.val2 or d['val2']
"""
__getattr__ = dict.__getitem__
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
def __init__(self, dct):
for key, value in dct.items():
if hasattr(value, 'keys'):
value = DotDict(value)
self[key] = value
dct = {'scalar_value':1, 'nested_dict':{'value':2, 'nested_nested': {'x': 21}}}
dct = DotDict(dct)
print dct.nested_dict.nested_nested.x
It looks a bit dangerous and error prone, not to mention source of countless surprises to other developers, but seems to be working.
Shamelessly plugging my own package
There is a package doing exactly what you want and also something more and it is called Prodict.
from prodict import Prodict
life_dict = {'bigBang':
{'stars':
{'planets': []}
}
}
life = Prodict.from_dict(life_dict)
print(life.bigBang.stars.planets)
# prints []
# you can even add new properties dynamically
life.bigBang.galaxies = []
PS 1: I'm the author of the Prodict.
PS 2: This is a direct copy paste of an answer of another question.