Nested defaultdicts in Python
Share
While this requirement may seem rare, learning how to nest defaultdicts
properly in Python can be extremely powerful and save you from bloated and confusing initialization code.
In this guide we’ll cover:
- What are nested dictionaries?
- What is a
defaultdict
, and when is it used? - How can we create multi-level
defaultdicts
(or nesteddefaultdicts
)?
If you’re already familiar with these concepts, feel free to skip ahead to step three.
We highly recommend you also read and learn about Python dictionary comprehension as well, after reviewing this guide.
1 – What is a nested dictionary?
In Python, a dictionary is a key-value map, and the value can contain any type. You can merge dictionaries and in this case, even nest them. A “nested” dictionary simply refers to a dictionary whose values are also dictionaries, and this pattern could be used for multiple levels.
Merge Dictionaries in Python
See the following example:
level_one = {
'level_two': {
'level_three': {
'some_key': 'some_value'
}
}
}
To access values in the terminal dictionary, you could use the following:
level_one['level_two']['level_three']['some_key']
2 – What is a defaultdict?
A defaultdict
is a dictionary with some added functionality. The defaultdict
allows us to specify a default type to return if a key doesn’t exist.
Typically, if you try to access a non-existent key in a dictionary, it will raise a KeyError
. But sometimes, rather than raising an exception, we may want to return a default value instead. There are a few ways to accomplish this, but one powerful tool is the defaultdict
. See the following:
from collections import defaultdict
my_dict = defaultdict(int)
We’ve instantiated a defaultdict
and set the default type to int
. This means if we try to access a key in my_dict
that doesn’t exist, it will return the value of int()
(which is 0
).
This is very powerful for a few reasons!
It always returns a value
my_dict['tyler'] = 34
print(my_dict['tyler'])
# => 34
print(my_dict['nikki'])
# => 0
We can operate on any key without initializing it
What is even more valuable is that we can operate on keys without initializing them. Imagine we want to use a dictionary to keep track of players’ scores.
scores = defaultdict(int)
# Tyler scores one point
scores['tyler'] += 1
# Nikki scores two points
scores['nikki'] += 2
The code is much more concise. With a standard dictionary, we’d have to check if the key exists and initialize the values up front.
3 – Nested defaultdicts
Now that we understand nested dictionaries and defaultdicts
, we can get into nested defaultdicts
.
This is concept is extremely powerful as it allows you to build complex dictionaries with a simple initialization. The only caveat is that you need to know the depth of your data structure in advance. Also, you need to know the default type of the terminal values.
If you’re wondering how this concept will be useful, think about a situation where you’d want to use a defaultdict
but you actually want the default
type to be defaultdict
.
The first thing we have to do is define our dictionary:
from collections import defaultdict
my_dict = defaultdict(lambda: defaultdict(dict))
Notice that we have to use a lambda
function as the argument to the first defaultdict
. This is because defaultdict
expects a callable (or None
).
If necessary, we could take this concept as far as we need:
my_dict = defaultdict(lambda: defaultdict(lambda: defaultdict(dict)))
Now, using our original example, we can easily populate our data structure without having to initialize each value.
my_dict['dogs']['rover']['bathed'] = True
my_dict['dogs']['rover']['fed'] = False
my_dict['cats']['sam']['fed'] = True