2019-03-04 04:53:46 -05:00
|
|
|
PEP: 585
|
2019-09-17 17:54:57 -04:00
|
|
|
Title: Type Hinting Generics In Standard Collections
|
2019-03-04 04:53:46 -05:00
|
|
|
Version: $Revision$
|
|
|
|
Last-Modified: $Date$
|
2019-04-16 10:50:15 -04:00
|
|
|
Author: Łukasz Langa <lukasz@python.org>
|
2019-09-17 17:54:57 -04:00
|
|
|
Discussions-To: Typing-Sig <typing-sig@python.org>
|
2019-03-04 04:53:46 -05:00
|
|
|
Status: Draft
|
|
|
|
Type: Standards Track
|
|
|
|
Content-Type: text/x-rst
|
2019-04-16 10:50:15 -04:00
|
|
|
Created: 03-Mar-2019
|
2019-09-17 17:54:57 -04:00
|
|
|
Python-Version: 3.9
|
2019-03-04 04:53:46 -05:00
|
|
|
|
|
|
|
Abstract
|
|
|
|
========
|
|
|
|
|
|
|
|
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
|
2019-09-17 17:54:57 -04:00
|
|
|
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``).
|
2019-03-04 04:53:46 -05:00
|
|
|
|
2019-09-17 17:54:57 -04:00
|
|
|
This PEP proposes to enable support for the generics syntax in all
|
|
|
|
standard collections available by the ``typing`` module.
|
2019-03-04 04:53:46 -05:00
|
|
|
|
|
|
|
|
|
|
|
Rationale and Goals
|
|
|
|
===================
|
|
|
|
|
2019-09-17 17:54:57 -04:00
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
Terminology
|
|
|
|
===========
|
|
|
|
|
|
|
|
Generic (n.) - a type that can be parametrized, typically a container.
|
|
|
|
Also known as a *parametric type* or a *generic type*. For example:
|
|
|
|
``dict``.
|
|
|
|
|
|
|
|
Parametrized generic - a specific instance of a generic with the
|
|
|
|
expected types for container elements provided. For example:
|
|
|
|
``dict[str, int]``.
|
|
|
|
|
2019-03-04 04:53:46 -05:00
|
|
|
|
|
|
|
Backwards compatibility
|
|
|
|
=======================
|
|
|
|
|
2019-09-17 17:54:57 -04:00
|
|
|
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 use generics in
|
|
|
|
combination with standard library collections.
|
2019-03-04 04:53:46 -05:00
|
|
|
|
|
|
|
Tooling, including type checkers and linters, will have to be adapted to
|
2019-09-17 17:54:57 -04:00
|
|
|
recognize such generics usage as valid.
|
|
|
|
|
2019-03-04 04:53:46 -05:00
|
|
|
|
|
|
|
Implementation
|
|
|
|
==============
|
|
|
|
|
|
|
|
Starting with Python 3.7, when ``from __future__ import annotations`` is
|
|
|
|
used, function and variable annotations can specify generics directly on
|
2019-09-17 17:54:57 -04:00
|
|
|
builtin types. Example::
|
2019-03-04 04:53:46 -05:00
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
def find(haystack: dict[str, list[int]]) -> int:
|
|
|
|
...
|
|
|
|
|
2019-09-17 17:54:57 -04:00
|
|
|
Certain features of typing like type aliases or casting require putting
|
|
|
|
types in runtime context, outside of annotations. 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 gain `__class_getitem__()` support
|
|
|
|
for generics:
|
|
|
|
|
|
|
|
* ``tuple`` # typing.Tuple
|
|
|
|
* ``list`` # typing.List
|
|
|
|
* ``dict`` # typing.Dict
|
|
|
|
* ``set`` # typing.Set
|
|
|
|
* ``frozenset`` # typing.FrozenSet
|
|
|
|
* ``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
|
|
|
|
* ``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
|
|
|
|
* ``contextlib.AbstractAsyncContextManager`` # typing.AsyncContextManager
|
|
|
|
|
|
|
|
Importing those from ``typing`` is deprecated. 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.
|
|
|
|
|
|
|
|
|
|
|
|
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 parametrized 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 parametrized type;
|
|
|
|
* the ``__origin__`` attribute points at the non-parametrized
|
|
|
|
generic class;
|
|
|
|
* the ``__parameters__`` attribute is a tuple (possibly of length
|
|
|
|
1) of generic types passed to the original ``__class_getitem__``;
|
|
|
|
* the ``__class_getitem__`` raises an exception to disallow mistakes
|
|
|
|
like ``dict[str][str]``.
|
|
|
|
|
|
|
|
This design means that it is possible to create instances of
|
|
|
|
parametrized collections, like::
|
|
|
|
|
|
|
|
>>> l = list[str]()
|
|
|
|
[]
|
|
|
|
>>> isinstance([1, 2, 3], list[str])
|
|
|
|
True
|
|
|
|
>>> list is list[str]
|
|
|
|
False
|
|
|
|
>>> list == list[str]
|
|
|
|
True
|
|
|
|
|
|
|
|
Objects created with bare types and parametrized types are exactly the
|
|
|
|
same. The generic parameters are not preserved in instances created
|
|
|
|
with parametrized 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 parametrized type. This provides symmetry between::
|
|
|
|
|
|
|
|
l: list[str] = []
|
|
|
|
|
|
|
|
and::
|
|
|
|
|
|
|
|
l = list[str]()
|
|
|
|
|
|
|
|
|
|
|
|
Forward compatibility
|
|
|
|
---------------------
|
|
|
|
|
|
|
|
Future standard collections must implement the same behavior.
|
2019-03-04 04:53:46 -05:00
|
|
|
|
|
|
|
|
2019-09-17 17:54:57 -04:00
|
|
|
Rejected alternatives
|
|
|
|
=====================
|
2019-03-04 04:53:46 -05:00
|
|
|
|
2019-09-17 17:54:57 -04:00
|
|
|
Do nothing
|
|
|
|
----------
|
2019-03-04 04:53:46 -05:00
|
|
|
|
2019-09-17 17:54:57 -04:00
|
|
|
Keeping the status quo forces Python programmers to perform book-keeping
|
|
|
|
of imports from the ``typing`` module for standard collections, making
|
|
|
|
all but the simplest annotations cumbersome to maintain. The existence
|
|
|
|
of parallel types is confusing to newcomers (why is there both ``list``
|
|
|
|
and ``List``?).
|
|
|
|
|
|
|
|
The above problems also don't exist in user-built generic classes which
|
|
|
|
share runtime functionality and the ability to use them as generic type
|
|
|
|
annotations. Making standard collections harder to use in type hinting
|
|
|
|
from user classes hindered typing adoption and usability.
|
|
|
|
|
|
|
|
Generics erasure
|
|
|
|
----------------
|
|
|
|
|
|
|
|
It would be easier to implement ``__class_getitem__`` on the listed
|
|
|
|
standard collections in a way that doesn't preserve the generic type,
|
|
|
|
in other words::
|
|
|
|
|
|
|
|
>>> list[str]
|
|
|
|
<class 'list'>
|
|
|
|
>>> tuple[int, ...]
|
|
|
|
<class 'tuple'>
|
|
|
|
>>> collections.ChainMap[str, list[str]]
|
|
|
|
<class 'collections.ChainMap'>
|
|
|
|
|
|
|
|
This is problematic as it breaks backwards compatibility: current
|
|
|
|
equivalents of those types in the ``typing`` module **do** preserve
|
|
|
|
the generic type::
|
|
|
|
|
|
|
|
>>> from typing import List, Tuple, ChainMap
|
|
|
|
>>> List[str]
|
|
|
|
typing.List[str]
|
|
|
|
>>> Tuple[int, ...]
|
|
|
|
typing.Tuple[int, ...]
|
|
|
|
>>> ChainMap[str, List[str]]
|
|
|
|
typing.ChainMap[str, typing.List[str]]
|
|
|
|
|
|
|
|
As mentioned in the "Implementation" section, preserving the generic
|
|
|
|
type at runtime enables runtime introspection of the type which can be
|
|
|
|
used for API generation or runtime type checking. Such usage is already
|
|
|
|
present in the wild.
|
|
|
|
|
|
|
|
Additionally, implementing subscripts as identity functions would make
|
|
|
|
Python less friendly to beginners. Let's demonstrate this with an
|
|
|
|
example. If a user is passing a list type instead of a list object to
|
|
|
|
a function, and that function is using indexing, the code would no
|
|
|
|
longer raise an error.
|
|
|
|
|
|
|
|
Today::
|
|
|
|
|
|
|
|
>>> l = list
|
|
|
|
>>> l[-1]
|
|
|
|
TypeError: 'type' object is not subscriptable
|
|
|
|
|
|
|
|
With ``__class_getitem__`` as an identity function::
|
|
|
|
|
|
|
|
>>> l = list
|
|
|
|
>>> l[-1]
|
|
|
|
list
|
|
|
|
|
|
|
|
The indexing being successful here would likely end up raising an
|
|
|
|
exception at a distance and with a confusing error message to the user.
|
|
|
|
|
|
|
|
Disallowing instantiation of parametrized types
|
|
|
|
-----------------------------------------------
|
|
|
|
|
|
|
|
Given that the proxy type which preserves ``__origin__`` and
|
|
|
|
``__parameters__`` is mostly useful for static analysis or runtime
|
|
|
|
introspection purposes, we might have disallowed instantiation of
|
|
|
|
parametrized types.
|
|
|
|
|
|
|
|
In fact, this is what the ``typing`` module does today for the parallels
|
|
|
|
of builtin collections only. Instantiation of other parametrized types
|
|
|
|
is allowed.
|
|
|
|
|
|
|
|
The original reason for this decision was to discourage spurious
|
|
|
|
parametrization which made object creation up to two orders of magnitude
|
|
|
|
slower compared to the special syntax available for builtin types.
|
|
|
|
|
|
|
|
This rationale is not strong enough to allow the exceptional treatment
|
|
|
|
of builtins. All other parametrized types can still be instantiated,
|
|
|
|
including parallels of collections in the standard library. Moreover,
|
|
|
|
Python allows for instantiation of lists using ``list()`` and some
|
|
|
|
builtin collections don't provide special syntax for instantiation.
|
|
|
|
|
|
|
|
Making ``isinstance(obj, list[str])`` perform a runtime type check
|
|
|
|
------------------------------------------------------------------
|
2019-03-04 04:53:46 -05:00
|
|
|
|
2019-09-17 17:54:57 -04:00
|
|
|
This functionality requires iterating over the collection which is
|
|
|
|
a destructive operation in some of them. This functionality would have
|
|
|
|
been useful, however implementing the type checker within Python that
|
|
|
|
would deal with complex types, nested type checking, type variables,
|
|
|
|
string forward references, and so on is out of scope for this PEP. This
|
|
|
|
can be revised in the future.
|
2019-03-04 04:53:46 -05:00
|
|
|
|
|
|
|
|
2019-09-17 17:54:57 -04:00
|
|
|
Note on the initial draft
|
|
|
|
=========================
|
2019-03-04 04:53:46 -05:00
|
|
|
|
2019-09-17 17:54:57 -04:00
|
|
|
An early version of this PEP discussed matters beyond generics in
|
|
|
|
standard collections. Those unrelated topics were removed for clarity.
|
2019-03-04 04:53:46 -05:00
|
|
|
|
|
|
|
|
2019-09-17 17:54:57 -04:00
|
|
|
Copyright
|
|
|
|
=========
|
2019-03-04 04:53:46 -05:00
|
|
|
|
2019-09-17 17:54:57 -04:00
|
|
|
This document is placed in the public domain or under the
|
|
|
|
CC0-1.0-Universal license, whichever is more permissive.
|