2

Is it possible to extend the approach discussed here to nested defaultdict?

EDIT:

As per comment, the default is updated from the original None to lambda: None. However, the following still doesn't work as intended:

from collections import defaultdict
dd = defaultdict(lambda: None, {"a":1,"b":{"c":3}})

dd["b"]["e"] raises a KeyError instead returning None.

How to convert all nested dict's to defaultdict?

alancalvitti
  • 476
  • 3
  • 14

3 Answers3

2

You could do:

from collections import defaultdict


def to_none(d, factory):
    result = defaultdict(factory)
    for key, value in d.items():
        if isinstance(value, dict):
            result[key] = to_none(value, factory)
        else:
            result[key] = value
    return result


d = {"a": 1, "b": {"c": 3}}

dd = to_none(d, lambda: None)

print(dd['a'])
print(dd['xxx'])
print(dd['b']['c'])
print(dd['b']['e'])

Output

1
None
3
None
Dani Mesejo
  • 61,499
  • 6
  • 49
  • 76
  • Seems like you're manually flattening the input dict and wrapping defaultdict where there's a dict. In general the input is more complicated with more nesting - manual process doesn't scale. – alancalvitti Dec 28 '18 at 16:49
  • @alancalvitti So what you want is a sort of mapping-like data-structure that returns `None` for every (nested) key that is not present? – Dani Mesejo Dec 28 '18 at 16:53
  • @alancalvitti Updated the answer, to include a function for any depth – Dani Mesejo Dec 28 '18 at 17:06
  • @Idlehands can you give me an example? After all dd is a defaultdict – Dani Mesejo Dec 28 '18 at 17:48
  • @DanielMesejo Sorry I was mistaken, honestly don't know what I was thinking. Guess I was too fixated on `to_none` being a function but wasn't thinking what is being `return`ed. – r.ook Dec 28 '18 at 17:59
1

collections.defaultdict isn't the ideal tool for this purpose. To specify None as a default value for a nested dictionary, you can just iterate your dictionary recursively and use dict.get to return None when any key at any level is not found:

from functools import reduce

def get_from_dict(dataDict, mapList):
    """Iterate nested dictionary"""
    return reduce(dict.get, mapList, dataDict)

d = {"a": 1, "b": {"c": 3}}

get_from_dict(d, ['b', 'e'])  # None
get_from_dict(d, ['b', 'c'])  # 3
jpp
  • 159,742
  • 34
  • 281
  • 339
0
def _sub_getitem(self, k):
    try:
        # sub.__class__.__bases__[0]
        real_val = self.__class__.mro()[-2].__getitem__(self, k)
        val = '' if real_val is None else real_val
    except Exception:
        val = ''
        real_val = None
    # isinstance(Avoid,dict)也是true,会一直递归死
    if type(val) in (dict, list, str, tuple):
        val = type('Avoid', (type(val),), {'__getitem__': _sub_getitem, 'pop': _sub_pop})(val)
        # 重新赋值当前字典键为返回值,当对其赋值时可回溯
        if all([real_val is not None, isinstance(self, (dict, list)), type(k) is not slice]):
            self[k] = val
    return val


def _sub_pop(self, k=-1):
    try:
        val = self.__class__.mro()[-2].pop(self, k)
        val = '' if val is None else val
    except Exception:
        val = ''
    if type(val) in (dict, list, str, tuple):
        val = type('Avoid', (type(val),), {'__getitem__': _sub_getitem, 'pop': _sub_pop})(val)
    return val


class DefaultDict(dict):
    def __getitem__(self, k):
        return _sub_getitem(self, k)

    def pop(self, k):
        return _sub_pop(self, k)

In[8]: d=DefaultDict({'balabala':"dddddd"})
In[9]: d['a']['b']['c']['d']
Out[9]: ''
In[10]: d['a']="ggggggg"
In[11]: d['a']
Out[11]: 'ggggggg'
In[12]: d['a']['pp']
Out[12]: ''

No errors again. No matter how many levels nested. pop no error also you can change to any value if you want.

ACE Fly
  • 305
  • 2
  • 8