Nested dictionary in Python: Assigning keys, BUT keeping sub structure

Question:

I am using Python 3.5 and I have a problem assigning values to a dictionary key. The structure of my dictionary looks like this:

dict_var = {'file':
               {'index':
                    {'flag':
                         {'flag_key': 'False'},
                     'attr':
                         {'attr_key': 'attr_val'},
                     'path':
                         {'path_key': 'path_val'},
                     }
                }
            }

I get the KeyError: 1, if I change the nested key 'index' like this:

dict_var['file']['1']['flag'] = 'some value'
dict_var['file']['2']['flag'] = 'some value'
dict_var['file']['3']['flag'] = 'some value'
dict_var['file']['4']['flag'] = 'some value'

Or if I try to change the nested key 'flag':

dict_var['file']['index']['flag_2']['flag_key'] = 'some value'

Is there a way to assign a new name to a nested key, but keep the structure of the following sub keys and values, like in my example?

Asked By: sunwarr10r

||

Answers:

You could simply transfer the values to the new key, then delete the old one. For example:

dict_var['file']['1'] = dict_var['file']['index']
del dict_var['file']['index']
Answered By: Blue Star

You can use a nested defaultdict, like this:

from collections import defaultdict

ndefaultdict = lambda:defaultdict(ndefaultdict)

dict_var = ndefaultdict()
dict_var['file']['1']['flag'] = 'some value'
dict_var['file']['2']['flag'] = 'some value'
dict_var['file']['3']['flag'] = 'some value'
dict_var['file']['4']['flag'] = 'some value'

You can then write a simple loop to transfer the information from your original dict into the nested dict.
An example solution:

from collections import defaultdict

def ndefaultdict(orig_dict):
    l = lambda: defaultdict(l)
    out = l()

    def n_helper(orig_dict, nesteddict):
        for k, v in orig_dict.items():
            if isinstance(v, dict):
                n_helper(v, nesteddict[k])
            else:
                nesteddict[k] = v
        return nesteddict

    return n_helper(orig_dict, out)

# dict_var is the original dictionary from the OP.
new_dict = n_defaultdict(dict_var)
new_dict['foo']['bar']['baz'] = 'It works!!'

print( new_dict['file']['index']['attr']['attr_key']) # attr_val

EDIT:

Looking at this SO thread, I found two other elegant solutions:

  1. From user Vincent

Short solution with defaultdict

from collections import defaultdict

def superdict(arg=()):
    update = lambda obj, arg: obj.update(arg) or obj
    return update(defaultdict(superdict), arg)

>>> d = {"a":1}
>>> sd = superdict(d)
>>> sd["b"]["c"] = 2
  1. From user Dvd Avins

Using a custom NestedDict class.

>>> class NestedDict(dict):
...     def __getitem__(self, key):
...         if key in self: return self.get(key)
...         return self.setdefault(key, NestedDict())


>>> eggs = NestedDict()
>>> eggs[1][2][3][4][5]
{}
>>> eggs
{1: {2: {3: {4: {5: {}}}}}}
Answered By: dangom