How to Create Defaultdict of Defaultdict in Python

Olorunfemi Akinlua Feb 02, 2024
How to Create Defaultdict of Defaultdict in Python

Python has built-in containers such as dict, list, set, and tuple, which have a general-purpose ideology. To extend or improve these general-purpose containers, Python provides a module that introduces specialized container datatypes called Python Collections.

One such specialized containers datatype is defaultdict, which is a great alternative (and a subclass) to the default Python dictionaries (dict). With defaultdict, you can provide a factory function to supply missing values (rather than a KeyError exception or message).

So, if the key is not present within the dictionary, the factory function is called and returns a value rather than raising a KeyError exception.

Using defaultdict might be relatively simple, but working with defaultdict of defaultdict can be confusing. This article will explain how to create defaultdict of defaultdict without causing any errors and how its intrinsic operations happen.

Use lambda to Create Defaultdict of Defaultdict in Python

To make use of Python Collections and inherently defaultdict, you can import the collection module using the Python expression:

from collections import defaultdict

Note the defaultdict is a subclass of the dict class, which the following Python expression can check:

issubclass(defaultdict, dict)

Output:

True

With dict, when a key that doesn’t exist is passed to the dictionary, it triggers the __missing__ method, which holds a default_factory attribute set to None, and therefore results in a KeyError exception. However, with default_dict, when a key that doesn’t exist is passed to the dictionary, it triggers the __missing__ method’s default_factory attribute, which holds a factory that returns a default value.

For example, we can have a defaultdict dictionary that holds the factory function, list, which returns an empty list when a non-existent key is passed.

from collections import defaultdict

ddict = defaultdict(list)
print(ddict["one"])

Output:

[]

Though ddict doesn’t have the key one, it returns the value of an empty list because the factory function is passed. It even creates the key after such an expression.

from collections import defaultdict

ddict = defaultdict(list)
print(ddict["one"])
print(ddict["two"].append(1))

print(ddict)

Output:

[]
defaultdict(<class 'list'>, {'one': [], 'two': [1]})

So, after the ddict["one"] and ddict["two"].append(1) statements, it creates the respective keys and a corresponding value based on the list function. For the second Python statement, it creates the empty list based on the default_factory attribute function and then appends the value 1 to it.

The typical grouping of values within a defaultdict datatype can be handled differently than with a dict datatype.

sentence = "the man loves oranges, but also cares a great deal about apples"

letterStore = dict()

for i in sentence:
    if k not in letterStore:
        letterStore[i] = 1
        continue
    letterStore[i] += 1

print(letterStore.items())

Output:

dict_items([('t', 4), ('h', 1), ('e', 7), (' ', 11), ('m', 1), ('a', 9), ('n', 2), ('l', 4), ('o', 4), ('v', 1), ('s', 5), ('r', 3), ('g', 2), (',', 1), ('b', 2), ('u', 2), ('c', 1), ('d', 1), ('p', 2)])

The above grouping of the letters can be done easily with the use of defaultdict instead. Rather than have the code block that checks if the letters are already in the letterStore binding to create an initial numbering, we can use defaultdict to achieve this with a factory function - int.

from collections import defaultdict

sentence = "the man loves oranges, but also cares a great deal about apples"

letterStore = defaultdict(int)

for i in sentence:
    letterStore[i] += 1

print(letterStore.items())

Output:

dict_items([('t', 4), ('h', 1), ('e', 7), (' ', 11), ('m', 1), ('a', 9), ('n', 2), ('l', 4), ('o', 4), ('v', 1), ('s', 5), ('r', 3), ('g', 2), (',', 1), ('b', 2), ('u', 2), ('c', 1), ('d', 1), ('p', 2)])

So, with this, we know that when a key doesn’t exist, the __missing__ method is called. Its attribute default_factory is also triggered, which holds a function that returns a value.

However, can we create a defaultdict of a defaultdict? Yes, but how can we do it? Because if you pass a defaultdict to another defaultdict, it will cause an error.

from collections import defaultdict

d = defaultdict(defaultdict(int))
print(d)

Output:

Traceback (most recent call last):
  File "c:\Users\USER\Desktop\JS\test.py", line 3, in <module>
    d = defaultdict(defaultdict(int))
TypeError: first argument must be callable or None

A TypeError is thrown when we run the code, and this happened because of the line d = defaultdict(defaultdict(int)), which says the first argument must be callable or None.

With this information, we can deduce that we didn’t pass a callable (a function) or None (default value that the default_factory holds), and that’s because defaultdict(int) is not callable. It is, however, a 'collections.defaultdict'.

Therefore, we need to find a way to pass a callable, where lambda comes in.

lambda allows us to create an anonymous function that can be called (a callable). So, for the upper level defaultdict, we can pass a lambda function that points to the defaultdict(int), which will be called when we pass a non-existent key.

The lambda function calls the factory function within the inner defaultdict and returns its value, which will be set as the key value.

from collections import defaultdict

d = defaultdict(lambda: defaultdict(int))
print(d)

Output:

defaultdict(<function <lambda> at 0x000001F6B9383E20>, {})

To show that it works fine, we can access the top-level defaultdict and the inner-level defaultdict using the square notation to see their default values which should be passed on the lambda and int functions, respectively.

print(d[0])
print(d[0][0])

Output:

defaultdict(<class 'int'>, {})
0
Olorunfemi Akinlua avatar Olorunfemi Akinlua avatar

Olorunfemi is a lover of technology and computers. In addition, I write technology and coding content for developers and hobbyists. When not working, I learn to design, among other things.

LinkedIn

Related Article - Python Dictionary