Python: Define Generic Types for Lists of Nested Dictionaries

Updated: February 17, 2024 By: Guest Contributor Post a comment

Overview

Working with nested dictionaries in Python is a common task, especially when dealing with configurations, JSON data, or complex data structures. When you start incorporating type hints into your code, you may wonder how to properly type these nested structures for better code readability, error checking, and editor support. This tutorial will guide you through defining generic types for lists of nested dictionaries in Python.

Understanding Type Hints in Python

Before diving into nested dictionaries, let’s briefly touch upon type hints. Type hints are a feature introduced in Python 3.5 through PEP 484, allowing developers to indicate the expected data types of function arguments, return values, and variable declarations. While these hints do not enforce type checking at runtime, they can be used by third-party tools, IDEs, and linters to identify potential errors.

def greeting(name: str) -> str:
    return 'Hello, ' + name

Generic Types for Dictionaries

When dealing with dictionaries, you can specify the types of keys and values using Dict from typing module or dict in Python 3.9+.

from typing import Dict

simple_dict: Dict[str, int] = {'count': 1}

Note: In Python 3.9+, you can also use the built-in dict type like so:

simple_dict: dict[str, int] = {'count': 1}

Typing Nested Dictionaries

For nested dictionaries, the complexity increases as you might have dictionaries within dictionaries or lists of dictionaries. To type these structures, you need to understand how to use typing.List and recursive types.

from typing import Dict, List

NestedDict = Dict[str, 'NestedDict']
ListNestedDict = List[NestedDict]

This approach defines a recursive type for a dictionary that can contain strings as keys and either values of the same type or lists of such dictionaries as values.

Example: Defining a Type for a Configuration

Let’s take an example where we want to define a type for a configuration that can be represented as a list of nested dictionaries. Assume the following structure:

configurations: ListNestedDict = [
    {
        "name": "config1",
        "settings": {
            "resolution": "1920x1080",
            "quality": "high"
        }
    },
    {
        "settings": {
            "resolution": "1280x720",
            "quality": "medium",
            "additional": {
                "codec": "h264",
                "bitrate": "4Mbps"
            }
        }
    }
]

In this structure, we have a list of dictionaries, each potentially containing nested dictionaries. By using our previously defined ListNestedDict type, we can specify that the configurations variable is expected to follow this structure. This helps in documenting the code and assists tools in type checking.

Generics with TypeVars

For more flexibility, especially when you’re not sure about the types of the keys or values in the nested dictionaries, Python’s typing module provides TypeVar and Generic.

from typing import TypeVar, Generic, List, Dict

K = TypeVar('K')
V = TypeVar('V')
class GenericNestedDict(Dict[K, V], Generic[K, V]):
    pass

ListGenericNestedDict = List[GenericNestedDict[str, Any]]

This code snippet introduces a generic class that can be used for nested dictionaries with arbitrary types for keys and values. It opens up possibilities for structuring data more dynamically while maintaining type hints.

Advanced Usage: Recursive Types with Protocols

Moving towards more advanced usage, the typing module’s Protocol can be used to define a recursive type that better expresses the variety and versatility of nested dictionary structures.

from typing import Protocol, TypeVar, Dict

class SupportsNestedDict(Protocol):
    def __getitem__(self, k: str) -> Union['SupportsNestedDict', str, int, List['SupportsNestedDict']]:
        ...

NestedDictType = TypeVar('NestedDictType', bound=SupportsNestedDict)

Protocols allow for structural subtyping, which is more flexible and robust compared to nominal subtyping with classes. It ensures that any type with a __getitem__ method that follows the signature will match the SupportsNestedDict protocol.

Conclusion

Python’s type hinting provides a powerful mechanism to clarify complex data structures like lists of nested dictionaries. Whether you’re working on configurations, APIs, or any application dealing with nested data, properly typing your data structures can drastically improve the maintainability and readability of your code. Begin by using simple generic types and gradually move towards more dynamic and recursive approaches as needed. Embrace these techniques to make your Python code more type-safe and robust.