Dictionary
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']}")