Here is a version which creates a dict, which items are accessible either by regular indexing or by attribute name.
It can be initialized from dicts and/or named arguments, e.g.
my_dict = DotDict(dict(a=1, b=2, c=3), d=4)
It can have any number of nested levels, e.g.
my_dict.a.b.c=1
# A dict which items are accessible either by indexing or as attributesclass DotDict(dict): def __init__(self, *args, **kwargs): super().__init__(self) for arg in args: # An unnamed argument can only be a dict (or a DotDict) if not isinstance(arg, dict): raise(TypeError(f'DotDict accepts only dict arguments or keyword arguments: {arg}')) # 'args' is a list of dicts and 'kwargs' is a dict, merge everthing all_items = kwargs for d in args: all_items = all_items | d # Dict items and named arguments are expanded at the root of the DotDict all_keys = all_items.keys() all_values = all_items.values() self._map_level(all_keys, all_values) # Add entries recursively def _map_level(self, keys, values): for k, v in zip(keys, values): if isinstance(v, dict): # A dict at this level means entries are part of a single map entry # at this level. The content of the map is determined recursively self[k] = DotDict() self[k]._map_level(v.keys(), v.values()) else: # v is not a dict, store the value as an attribute self[k] = v def __getattr__(self, attr): return self.__getitem__(attr) def __setattr__(self, key, value): self.__setitem__(key, value)
Use:
a_dict = dict(a1=1, a2=2)b_dict = dict(b1=3, b2=4)c_dict = dict(c1=5, c2=6)m = DotDict(a_dict, b_dict, c=c_dict, d=8, e=9)m.c.c3 = 7print(f'm.a1={m.a1}, m.b2={m.b2}, m.c.c1={m.c.c1}, m.d={m.d}, m.c.c3={m.c.c3}')
m.a1=1, m.b2=4, m.c.c1=5, m.d=8, m.c.c3=7