Nested defaultdicts in Python

While this requirement may seem rare, learning how to nest defaultdicts properly 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 nested defaultdicts)?

If you're already familiar with these concepts, feel free to skip ahead to step three.

1

In Python, a dictionary is a key-value map, and the value can contain any type. A "nested" dictionary simply refers to a dictionary whose values are also dictionaries, and this pattern could be used for multiple levels.

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

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

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