In this cheatsheet, we use a dictionary with types assigned for the keys and the values.

A typed dictionary is useful for defining a type to represent a recurring object structure or an object with many fields. Then your functions can be lighter to define.

Benefits

A dictionary, but with structure and types enforced to give you safety.

So you can validate that a dictionary passed around meets the following:

  • Object has all the fields it needs.
  • There are no unexpected fields.
  • Fields are of the correct type.
  • Fields are only null when allowed to be.

Use a plain dict

If you care about the type of the key and value but not the actual keys.

Here we create a type using a dictionary.

dict[KEY_TYPE, VALUE_TYPE]

# e.g.
dict[str, str]
dict[str, list[str]]

Type comment

From the Mypy Examples page.

I’ve only see this comment approach used for a dictionary, I don’t know why it is used here and not so much for others, but it seems like the preferred style.

d = {}  # type: dict[str, int]

# For older Python versions:
from typing import Dict

d = {}  # type: Dict[str, int]

You could make an alias, for reuse:

MyType = dict[str, int]

d = {}  # type: MyType

Type annotation

Based on Check dict items.

You can add the type like for other data types.

d: dict[str, int] = {}

# Resuable type.
MyType = dict[str, int]
d: MyType = {}

Use TypedDict

This approach is useful if you have specific keys you want to want to include. If you use dict[str, str] from above you can’t check keys. And you can’t use x = { 'abc': str } as your type because it won’t work, so we use TypedDict.

Here we define and use the TypeDict type. Example from TypedDict section of Mypy docs.

Define

from typing_extensions import TypedDict

A generic example, using a single field with type of string.

MyType = TypedDict(
    'MyType', 
    {
        'my_key': str, 
    }
)

Ways to use the type

Here are using a Movie type, defined as follows:

Movie = TypedDict('Movie', {'name': str, 'year': int})

A nested example:

FooBar = TypedDict(
    "FooBar",
    {
        "Id": str,
        "Status": TypedDict("Status", {"Id": int, "Name": str}),
    },
)

The 4 approaches are:

Annotation

Annotate the type with comment explicitly.

movie = {'name': 'Toy Story', 'year': 1995}  # type: Movie

Constructor

Use the type as a constructor. Similar to a class.

movie = Movie(name='Toy Story', year=1995)

Function parameter

def foo(movie: Movie) -> None:
    name = movie['name']
    print(name)

Class inheritance

from typing_extensions import TypedDict


class Point(TypedDict):
    x: int
    y: int


p: Point = {'x': 1, 'y': 4}

# Error: Incompatible types (expression has type "float",
#        TypedDict item "x" has type "int")
p: Point = {'x': 1.2, 'y': 4}

From Check TypedDict items.

Nesting objects

Example based on asking Rix AI for help.

Note that we define as class and use that to either annonate a dict or create a class instance, for the same results.

We validate here that an object has the value types as defined in Fruit, and that the value for the taste key follows dict insdie that matching the structure of FruitTaste.

from typing import TypedDict


class Fruit(TypedDict):
    name: str
    color: str 
    taste: 'FruitTaste'
    id: int

class FruitTaste(TypedDict):
    sweetness: int
    sourness: int

apple = Fruit({
    "name": "Apple", 
    "color": "red",
    "taste": {
        "sweetness": 8,
        "sourness": 2,
    },
    "id": 1
})

# This format also works the same for running or checking types.

orange: Fruit = {
    "name": "Orange",
    "color": "orange", 
    "taste": {
        "sweetness": 6,
        "sourness": 4,
    },
    "id": 2  
})

Errors

When you use a typed dictionary, Mypy can detect an invalid key or value type and give an error:

director = movie['director']
# Error: 'director' is not a valid key

Example

Here we define a Person type and use it in two functions.

from typing_extensions import TypedDict


Person = TypedDict(
    'Person', 
    {
        'name': str, 
        'age': int, 
        'height': Optional[float]
    }
)


def foo(person: Person) -> None:
    print(person['name'])
    print(person['age'] >= 18)
    print(person['height'])


def greet(person: Person) -> None:
    print(f"Hello, {person['name']}")