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