Search code examples
pythondictionarydefaultdict

Nested defaultdict raises AttributeError


I am using a nested defaultdict to keep code clean and reduce the redundant code.

I am constructing a dictionary such as:

{"Store1": {"Product1": 1}, "Store2": {"Product1": 2, "Product2": 1}}

I tried to implement the answer to this question Nested dictionary with defaults, which would raise the exception:

AttributeError: 'int' object has no attribute 'items'
from collections import defaultdict, Counter

d = defaultdict(lambda: defaultdict(lambda: Counter()))
d["Store1"]["Product1"] += 1
print(d)

is there anyway I can for example:

d["Store1"]["Product1"] += 1

Solution

  • When you use the following

    d = defaultdict(lambda: defaultdict(lambda: Counter()))
    d["Store1"]["Product1"] += 1
    

    then, d["Store1"] will create a new element of "type" defaultdict(lambda: Counter()) and thus d["Store1"]["Product1"] will create a new element of type Counter. Hence when you do += 1, it attempts to augment the Counter object by 1. This, however, is not defined, as the types are not compatible:

    >>> c = Counter()
    >>> c += 1
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/---/lib/python3.7/collections/__init__.py", line 832, in __iadd__
        for elem, count in other.items():
    AttributeError: 'int' object has no attribute 'items'
    

    As you can observe, it assumes that the right hand side follows the Mapping protocol and it attempts to add the r.h.s. counts to its own. I.e. something like:

    >>> c = Counter(a=1)
    >>> c += Counter(a=2, b=3)
    >>> c
    Counter({'a': 3, 'b': 3})
    

    When you use defaultdict(lambda: defaultdict(int)) on the other hand, then d["Store1"]["Product1"] creates a new int object, which can be incremented by += 1 and written back to the dict.