Search code examples
pythondictionarynestedkeyassign

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


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?


Solution

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