You must give up your requirement of returning None if you wish d[key][subkey] = value
to work with missing keys. If d[key] is None
, d[key][subkey] = value
is equivalent to None[subkey] = value
, which cannot work.
What you can do with missing values is to return an empty dict-like object without assigning it to a key just yet. If that object holds a reference to the parent, the assignment can be delayed until there is an explicit assignment down the hierachy.
An example implementation (this is incomplete, you must do more than override setitem to have a fully functional dict subclass):
class NestedDict(dict):
def __init__(self, parent=None, parentkey=None):
self.parent = parent
self.parentkey = parentkey
def __missing__(self, key):
return NestedDict(self, key)
def __setitem__(self, key, value):
if self.parent is not None:
self.parent[self.parentkey] = self
self.parent = None
super(NestedDict, self).__setitem__(key, value)
>>> d = NestedDict()
>>> d[1][2][3] = 4
>>> d[2]
{}
>>> d.keys()
[1]
>>> d[1][2][3]
4
An alternative approach would be to override __getitem__
and __setitem__
to do a nested lookup when the key is a tuple. This version gives a KeyError from __getitem__
for missing keys in order to be consistent with regular dict. You can change it easily to return None instead if you wish.
class NestedDict(dict):
def __getitem__(self, key):
if isinstance(key, tuple):
try:
x = self
for k in key:
x = x[k]
return x
except (KeyError, TypeError):
raise KeyError(key)
else:
return super(NestedDict, self).__getitem__(key)
def __setitem__(self, key, value):
if isinstance(key, tuple):
d = self
for k in key[:-1]:
d = d.setdefault(k, NestedDict())
d[key[-1]] = value
else:
super(NestedDict, self).__setitem__(key, value)
>>> d = NestedDict()
>>> d[1,2,3] = 4
>>> d[1,2,3]
4
>>> d[1,2,4]
KeyError: (1, 2, 4)
>>> d
{1: {2: {3: 4}}}