PEP: 9999 Title: Details of generic support for TypedDict Author: Samodya Abeysiriwardane <hi at sransara.com> Status: Draft Type: Standard Track Content-Type: text/x-rst Created: 19-Jul-2021 Python-Version: 3.11 Post-History:
PEP 589 [1] introduces a type constructor TypedDict
to support the
use case where a dictionary object has a specific set of string keys, each with
a value of a specific type.
But it does not specify the details of supporting generic TypedDicts, where the
type of a value can be parameterized.
This PEP specifies the details of a type parameterized TypedDicts as an
extension of PEP 589 [1].
TypedDict is a great choice to give a precise type for pure data containers. Almost all container data types of the standard library support generics. TypedDicts should also support generics.
PEP 589 [1] sets a requirement that a TypedDict can only inherit from a TypedDict. This PEP proposes to relax that requirement to allow TypedDict with Generic and layout the details of allowing generics.
Following is few example usecases for having generic TypedDicts.
Using type annotations to convey relationships between keys.
- class Page(TypedDict, Generic[T]):
- uid: Hash[T] desc: str item: T
An example where the function on container type does not concern with the concrete type of the contents.
- def get_items(*pages: Page[T]) -> List[T]:
- return [page["item"] for page in pages]
Another usage example in annotating polymorphic JSON responses.
- class Item(TypedDict):
- type: str
- class ValueItem(Item):
- value: int
T = TypeVar('T')
- class ItemPager(TypedDict, Generic[T]):
- next_page: Optional[str] items: List[T]
- def get_value_items() -> List[ValueItem]:
value_items: List[ValueItem] = [] url = "https://example.com/value_items" while url:
response = requests.get(url) pager: ItemPager[ValueItem] = response.json() url = pager['next_page'] value_items += pager['items']return value_items
This PEP is an extension of PEP 589 [1], hence details specified here for TypedDicts will be in addition to what is specified in PEP 589 [1].
Class based syntax for TypedDicts will follow the same specification as user defined generic types in PEP 484 [2] and class based syntax specification detailed in PEP 589 [1].
from typing import TypedDict, Generic, TypeVar
T = TypeVar("T")
- class Pager(TypedDict, Generic[T]):
- next_page: str items: List[T]
Generic[T]
as a base class defines that the class Pager
takes a
single type parameter T
. Pager[int]
is a TypedDict that has a
key items
with value type List[int]
.
TODO: This section needs be re-thought out because in implementation _collect_type_vars cannot see inside ForwardRef`s to find `TypeVars.
The following alternative syntax is semantically equivalent to the previous class based syntax example. Order of Generic subscript TypeVars will be in the order of their appearance. For Python versions before 3.7 where dict item order is not guaranteed, an ordered dictionary needs to be used with type variables.
from typing import TypedDict
T = TypeVar("T")
- Pager = TypedDict("Pager", {
- 'next_page': str, 'items': List[T],
})
A TypedDict can only inherit from another TypedDict or TypedDict with Generic. This is in contrast to PEP 589 [1], which specifies that a TypedDict can only inherit from a TypedDict.
Subclassing a generic TypedDict without specifying type parameters
assumes Any
for each position.
All other details of TypedDict inheritance from PEP 589 [1] applies.
Inheriting a generic TypedDict with specified parameters, would inherit annotations with type variable parameters replaced by those specified parameters.
- class A(TypedDict, Generic[T, VT], total=False):
- a1: List[T] a2: VT
- class B(A[KT, int]):
- b: KT
In the above example B inherits a2 with type int.
Here is an example of how the type Pager
can be used.
- pager: Pager[str] = {
- 'next_page': 'https://example.com/value_items?p=2', 'items': ['item 1', 'item2',]
}
If the type parameter is not specified, type checker can follow the same behavior as how it treats other generic instances without a type parameter.