Skip to content

Using the defaultdict Module: Explained with Examples

CodeMDD.io

Using the Python defaultdict Type for Handling Missing Keys

A common problem that you can face when working with Python dictionaries is trying to access or modify keys that don’t exist in the dictionary. This can result in a KeyError and break your code execution. To handle this issue, the standard library provides the Python defaultdict type, which is a dictionary-like class available in the collections module.

The Python defaultdict type behaves almost exactly like a regular Python dictionary, but it automatically creates the missing key and generates a default value for it if you try to access or modify it. This makes defaultdict a valuable option for handling missing keys in dictionaries.

In this tutorial, you will learn:

  • How to use the Python defaultdict type for handling missing keys in a dictionary
  • When and why to use a defaultdict instead of a regular dict
  • How to use a defaultdict for grouping, counting, and accumulating operations

To get the most out of this tutorial, you should have some previous understanding of what Python dictionaries are and how to work with them. If you need a refresher, you can check out the resources mentioned below:

  • Dictionaries in Python (Tutorial)
  • Dictionaries in Python (Course)
  • How to Iterate Through a Dictionary in Python

Handling Missing Keys in Dictionaries

A common issue you may encounter when working with Python dictionaries is how to handle missing keys. Dealing with frequent KeyError exceptions can be annoying and add complexity to your code. Fortunately, Python provides several ways to handle missing keys, including using the defaultdict type.

Understanding the Python defaultdict Type

The Python defaultdict type is a subclass of the built-in dict type. It overrides one method, missing(), which is called when a missing key is accessed or modified. Instead of raising a KeyError, defaultdict calls missing() and automatically creates the missing key with a default value.

To use defaultdict, you need to import it from the collections module:

from collections import defaultdict

Using the Python defaultdict Type

Grouping Items

One common use case for defaultdict is to group items in a sequence based on a certain property. Let’s say you have a list of numbers, and you want to group them by their remainder when divided by 2. You can achieve this with a defaultdict:

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
grouped_numbers = defaultdict(list)
for num in numbers:
grouped_numbers[num % 2].append(num)
print(grouped_numbers)

The output will be a defaultdict where the keys are the remainders (0 and 1) and the values are the corresponding numbers that have that remainder:

defaultdict(<class 'list'>, {1: [1, 3, 5, 7, 9], 0: [2, 4, 6, 8, 10]})

Grouping Unique Items

If you want to group unique items in a sequence, you can combine defaultdict with the set type:

names = ['Alice', 'Bob', 'Charlie', 'Alice', 'Bob', 'David', 'Charlie']
grouped_names = defaultdict(set)
for name in names:
grouped_names[name[0]].add(name)
print(grouped_names)

The output will be a defaultdict where the keys are the first letters of the names and the values are the unique names starting with that letter:

defaultdict(<class 'set'>, {'A': {'Alice'}, 'B': {'Bob'}, 'C': {'Charlie'}, 'D': {'David'}})

Counting Items

You can also use defaultdict to count occurrences of items in a sequence. For example, let’s count the frequency of each character in a string:

text = "Hello, World!"
character_counts = defaultdict(int)
for char in text:
character_counts[char] += 1
print(character_counts)

The output will be a defaultdict with the characters as keys and their frequencies as values:

defaultdict(<class 'int'>, {'H': 1, 'e': 1, 'l': 3, 'o': 2, ',': 1, ' ': 1, 'W': 1, 'r': 1, 'd': 1, '!': 1})

Accumulating Values

Another useful application of defaultdict is accumulating values. Suppose you have a list of sales data, and you want to calculate the total sales for each month. You can use defaultdict to create a dictionary with the months as keys and the accumulated sales as values:

sales_data = [
('Jan', 100),
('Feb', 200),
('Feb', 150),
('Mar', 300),
('Mar', 250),
('Mar', 200),
('Apr', 150),
]
monthly_sales = defaultdict(int)
for month, sales in sales_data:
monthly_sales[month] += sales
print(monthly_sales)

The output will be a defaultdict where the keys are the months and the values are the total sales for each month:

defaultdict(<class 'int'>, {'Jan': 100, 'Feb': 350, 'Mar': 750, 'Apr': 150})

Diving Deeper Into defaultdict

In addition to its basic usage, defaultdict offers some interesting features and differences compared to the regular dict type.

defaultdict vs dict

The main difference between defaultdict and dict is how they handle missing keys. While dict raises a KeyError, defaultdict automatically creates the missing key and generates a default value. This behavior can save you from writing extra code to handle missing keys.

defaultdict.default_factory

One important attribute of defaultdict is default_factory. It specifies the default value generator for missing keys. By default, default_factory is None, which means a missing key will produce None as the default value. However, you can assign any callable object as the default_factory to generate a custom default value. For example, if you want the default value to be an empty list instead of None, you can do:

my_dict = defaultdict(list)

Now, when you access or modify a missing key in my_dict, it will automatically create the key and assign an empty list as the default value.

defaultdict vs dict.setdefault()

dict also provides a setdefault() method to handle missing keys. This method allows you to set a default value for a missing key without raising a KeyError. However, it requires extra code compared to using defaultdict. Here’s an example:

my_dict = {}
if 'key' not in my_dict:
my_dict['key'] = 'default value'

With defaultdict, you can achieve the same result with just one line:

my_dict = defaultdict(lambda: 'default value')

defaultdict.missing()

Another attribute specific to defaultdict is missing(). It is a method that is called when a missing key is accessed or modified. You can override this method to customize its behavior. However, it’s rarely necessary to use missing() in practice.

Emulating the Python defaultdict Type

If you ever need to create a similar behavior to defaultdict on your own custom classes, you can emulate its functionality by overriding the getitem() method. This method is called when a key is accessed using the square bracket syntax. Here’s an example:

class MyContainer:
def __init__(self):
self.data = {}
def __getitem__(self, key):
try:
return self.data[key]
except KeyError:
self.data[key] = 'default value'
return self.data[key]

With this implementation, you can create your own container that automatically generates default values for missing keys.

Passing Arguments to .default_factory

You can also pass arguments to the default_factory of defaultdict. This can be useful if you want to generate default values based on some parameters. There are two common ways to achieve this: using lambda functions and using the functools.partial() function.

Using lambda

Lambda functions allow you to create anonymous functions on the fly. You can use them as the default_factory to pass arguments to it. Here’s an example:

from collections import defaultdict
defaulter = lambda x: x * 2
my_dict = defaultdict(defaulter, {1: 10, 2: 20, 3: 30})
print(my_dict[4])
print(my_dict[5])

In this example, defaulter is a lambda function that doubles its input. The defaultdict my_dict is initialized with a regular dictionary and the defaulter as its default_factory. When accessing missing keys, the defaulter function is called with the missing key as the argument.

Using functools.partial()

The functools module provides the partial() function, which allows you to fix a specific argument of a function and create a new function with that argument pre-filled. You can use functools.partial() to pass arguments to the default_factory. Here’s an example:

from collections import defaultdict
from functools import partial
def defaulter(x, y):
return x + y
my_dict = defaultdict(partial(defaulter, y=100), {1: 10, 2: 20, 3: 30})
print(my_dict[4])
print(my_dict[5])

In this example, defaulter is a function that adds two numbers. The functools.partial() function is used to create a new function with the y argument pre-filled with 100. The defaultdict my_dict is initialized with a regular dictionary and the partial function as its default_factory. When accessing missing keys, the partial function is called with the missing key as the x argument and the pre-filled y argument.

Conclusion

The Python defaultdict type is a powerful tool for handling missing keys in dictionaries. It automatically creates missing keys and generates default values, saving you from writing extra code to handle KeyError exceptions. defaultdict can be used for grouping, counting, and accumulating operations, making it a valuable option in various scenarios.

Now that you have a good understanding of the Python defaultdict type, you can start using it effectively in your own programming challenges.