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 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