I combined jpp and Olivier Melançon answer to create this a NestedDict class. I prefer to access a dict like you normally access a dict but then with a list as argument
import operator
from functools import reduce
class NestedDict(dict):
def __getitem__(self, item):
if isinstance(item, list):
return self.get_traverse(item)
return super(NestedDict, self).__getitem__(item)
def __setitem__(self, key, value):
if isinstance(key, list):
for _key in key[:-1]:
if not _key in self:
self[_key] = {}
self = self[_key]
self[key[-1]] = value
return self
return super(NestedDict, self).__setitem__(key, value)
def get_traverse(self, _list):
return reduce(operator.getitem, _list, self)
nested_dict = NestedDict({'aggs': {'aggs': {'field_I_want': 'value_I_want'}, 'None': None}})
path = ['aggs', 'aggs', 'field_I_want']
nested_dict[path] # 'value_I_want'
nested_dict[path] = 'changed'
nested_dict[path] # 'changed'
Edit
For anyone that is interested. I enhanced the class with a function to automatically find paths. (Usage in the doctest)
import operator
from functools import reduce
from copy import deepcopy
class NestedDict(dict):
"""
Dictionary that can use a list to get a value
:example:
>>> nested_dict = NestedDict({'aggs': {'aggs': {'field_I_want': 'value_I_want'}, 'None': None}})
>>> path = ['aggs', 'aggs', 'field_I_want']
>>> nested_dict[path]
'value_I_want'
>>> nested_dict[path] = 'changed'
>>> nested_dict[path]
'changed'
"""
def __getitem__(self, item):
if isinstance(item, list):
return self.get_traverse(item)
return super(NestedDict, self).__getitem__(item)
def __setitem__(self, key, value):
if isinstance(key, list):
for _key in key[:-1]:
if _key not in self:
self[_key] = {}
self = self[_key]
self[key[-1]] = value
return self
return super(NestedDict, self).__setitem__(key, value)
def get_traverse(self, _list):
return reduce(operator.getitem, _list, self)
_paths = []
_path = []
def find(self, key, _dict=None, _main_loop=True):
""" Find a list of paths to a key
:param key: str, the key you want to find
:param _dict: used for recursive searching
:return: list with paths
:example:
>>> nested_dict = NestedDict({'aggs': {'aggs': {'field_I_want': 'value_I_want'}, 'None': None}})
>>> paths = nested_dict.find('field_I_want')
>>> paths
[['aggs', 'aggs', 'field_I_want']]
>>> nested_dict[paths[0]] = 'changed'
>>> nested_dict[paths[0]]
'changed'
"""
if _main_loop:
self._path, self._paths = [], []
_dict = self
for _key in _dict.keys():
self._path.append(_key)
if _key == key:
self._paths.append(deepcopy(self._path))
if isinstance(_dict[_key], dict):
self.find(key, _dict[_key], _main_loop=False)
self._path.pop()
if _main_loop:
return self._paths