Nested defaultdicts in Python

John John (304)
2 minutes

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 nested defaultdicts)?

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.

Posted in these interests:
h/python67 guides
h/code69 guides

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.

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

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.

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
A comprehensive guide on Python "for" loops
John John (304)
0

Python, like many programming languages, implements containers, which are collections of other objects. As such, Python provides the for statement for iterating over each element in the collection.