Python TypedDict with Generics
Posted 1 July 2023
Python added support for type hints in 3.5 but it's suprisingly tricky to type a dict
using a generic type for values.
Defining type hints for a dictionary with a fixed set of keys is fairly straightforward with the TypedDict
class from the typing
module:
from typing import TypedDict
class Book(TypedDict):
id: int
name: str
book: Book = {"id": 123, "name": "To Kill A Mockingbird"}
If you add the line:
not_a_book: Book = {"something": "A book doesn't have"}
And run this with mypy you'll get:
$ python -m mypy type-dict.py
type-dict.py:9: error: Missing keys ("id", "name") for TypedDict "Book" [typeddict-item]
type-dict.py:9: error: Extra key "something" for TypedDict "Book" [typeddict-unknown-key]
Found 2 errors in 1 file (checked 1 source file)
Simple enough!
The typing
module also provides Generic
and TypeVar
to provide support for generic classes. In my case I wanted to define a generic class for a nested dict
with a value
key that could be a variable type. Extending our book example:
from typing import TypeVar, Generic, TypedDict
T = TypeVar('T')
class Attribute(TypedDict, Generic[T]):
value: T
class Book(TypedDict):
id: Attribute[int]
name: Attribute[str]
book: Book = {"id": {"value": 123}, "name": {"value": "To Kill A Mockingbird"}}
Type-check with mypy
and it works great! Try to run this however...
$ python --version
Python 3.10.12
$ python type-dict-generic.py
Traceback (most recent call last):
File "/home/aocallaghan/exp/py-type-dict/type-dict-generic.py", line 5, in <module>
class Attribute(TypedDict, Generic[T]):
File "/home/aocallaghan/.pyenv/versions/3.10.12/lib/python3.10/typing.py", line 2348, in __new__
raise TypeError('cannot inherit from both a TypedDict type '
TypeError: cannot inherit from both a TypedDict type and a non-TypedDict base class
You can't inherit from both a TypedDict
type and something else with Python 3.10. However this has been fixed in Python 3.11:
$ python --version
Python 3.11.4
$ python type-dict-generic.py
Unfortunately there doesn't seem to be a simple way to achieve this in versions <3.11. One option is to use the Enum
class to represent keys:
from enum import Enum, auto
from typing import TypeVar, TypedDict
T = TypeVar('T')
class AttributeKeys(Enum):
value = auto()
class Book(TypedDict):
id: dict[AttributeKeys, int]
name: dict[AttributeKeys, str]
book: Book = {"id": {AttributeKeys.value: 123}, "name": {AttributeKeys.value: "To Kill A Mockingbird"}}
not_a_book: Book = {"something": "A book doesn't have"}