This PEP proposes to enable support for the generics syntax in all standard collections currently available in the typing module.

Static typing as defined by PEPs 484, 526, 544, 560, and 563 was built incrementally on top of the existing Python runtime and constrained by existing syntax and runtime behavior. This led to the existence of a duplicated collection hierarchy in the typing module due to generics (for example typing.List and the built-in list ).

This change removes the necessity for a parallel type hierarchy in the typing module, making it easier for users to annotate their programs and easier for teachers to teach Python.

parameterized generic -- a specific instance of a generic with the expected types for container elements provided. Also known as a parameterized type. For example: dict[str, int] .

Generic (n.) -- a type that can be parameterized, typically a container. Also known as a parametric type or a generic type. For example: dict .

On the source level, the newly described functionality requires Python 3.9. For use cases restricted to type annotations, Python files with the "annotations" future-import (available since Python 3.7) can parameterize standard collections, including builtins. To reiterate, that depends on the external tools understanding that this is valid.

Tooling, including type checkers and linters, will have to be adapted to recognize standard collections as generics.

Starting with Python 3.7, when from __future__ import annotations is used, function and variable annotations can parameterize standard collections directly. Example:

from __future__ import annotations def find(haystack: dict[str, list[int]]) -> int: ...

Usefulness of this syntax before PEP 585 is limited as external tooling like Mypy does not recognize standard collections as generic. Moreover, certain features of typing like type aliases or casting require putting types outside of annotations, in runtime context. While these are relatively less common than type annotations, it's important to allow using the same type syntax in all contexts. This is why starting with Python 3.9, the following collections become generic using __class_getitem__() to parameterize contained types:

tuple # typing.Tuple

# typing.Tuple list # typing.List

# typing.List dict # typing.Dict

# typing.Dict set # typing.Set

# typing.Set frozenset # typing.FrozenSet

# typing.FrozenSet type # typing.Type

# typing.Type collections.deque

collections.defaultdict

collections.OrderedDict

collections.Counter

collections.ChainMap

collections.abc.Awaitable

collections.abc.Coroutine

collections.abc.AsyncIterable

collections.abc.AsyncIterator

collections.abc.AsyncGenerator

collections.abc.Iterable

collections.abc.Iterator

collections.abc.Generator

collections.abc.Reversible

collections.abc.Container

collections.abc.Collection

collections.abc.Callable

collections.abc.Set # typing.AbstractSet

# typing.AbstractSet collections.abc.MutableSet

collections.abc.Mapping

collections.abc.MutableMapping

collections.abc.Sequence

collections.abc.MutableSequence

collections.abc.ByteString

collections.abc.MappingView

collections.abc.KeysView

collections.abc.ItemsView

collections.abc.ValuesView

contextlib.AbstractContextManager # typing.ContextManager

# typing.ContextManager contextlib.AbstractAsyncContextManager # typing.AsyncContextManager

# typing.AsyncContextManager re.Pattern # typing.Pattern, typing.re.Pattern

# typing.Pattern, typing.re.Pattern re.Match # typing.Match, typing.re.Match

Importing those from typing is deprecated. Due to PEP 563 and the intention to minimize the runtime impact of typing, this deprecation will not generate DeprecationWarnings. Instead, type checkers may warn about such deprecated usage when the target version of the checked program is signalled to be Python 3.9 or newer. It's recommended to allow for those warnings to be silenced on a project-wide basis.

The deprecated functionality will be removed from the typing module in the first Python version released 5 years after the release of Python 3.9.0.

Parameters to generics are available at runtime Preserving the generic type at runtime enables introspection of the type which can be used for API generation or runtime type checking. Such usage is already present in the wild. Just like with the typing module today, the parameterized generic types listed in the previous section all preserve their type parameters at runtime: >>> list[str] list[str] >>> tuple[int, ...] tuple[int, ...] >>> ChainMap[str, list[str]] collections.ChainMap[str, list[str]] This is implemented using a thin proxy type that forwards all method calls and attribute accesses to the bare origin type with the following exceptions: the __repr__ shows the parameterized type;

shows the parameterized type; the __origin__ attribute points at the non-parameterized generic class;

attribute points at the non-parameterized generic class; the __args__ attribute is a tuple (possibly of length 1) of generic types passed to the original __class_getitem__ ;

attribute is a tuple (possibly of length 1) of generic types passed to the original ; the __parameters__ attribute is a lazily computed tuple (possibly empty) of unique type variables found in __args__ ;

attribute is a lazily computed tuple (possibly empty) of unique type variables found in ; the __getitem__ raises an exception to disallow mistakes like dict[str][str] . However it allows e.g. dict[str, T][int] and in that case returns dict[str, int] . This design means that it is possible to create instances of parameterized collections, like: >>> l = list[str]() [] >>> list is list[str] False >>> list == list[str] False >>> list[str] == list[str] True >>> list[str] == list[int] False >>> isinstance([1, 2, 3], list[str]) TypeError: isinstance() arg 2 cannot be a parameterized generic >>> issubclass(list, list[str]) TypeError: issubclass() arg 2 cannot be a parameterized generic >>> isinstance(list[str], types.GenericAlias) True Objects created with bare types and parameterized types are exactly the same. The generic parameters are not preserved in instances created with parameterized types, in other words generic types erase type parameters during object creation. One important consequence of this is that the interpreter does not attempt to type check operations on the collection created with a parameterized type. This provides symmetry between: l: list[str] = [] and: l = list[str]() For accessing the proxy type from Python code, it will be exported from the types module as GenericAlias . Pickling or (shallow- or deep-) copying a GenericAlias instance will preserve the type, origin, attributes and parameters.