|Title:||Type Hinting Usability Conventions|
|Author:||Łukasz Langa <lukasz at python.org>|
|Discussions-To:||Python-Dev <python-dev at python.org>|
- Status of this PEP
- Rationale and Goals
- Backwards compatibility
- Rejected alternatives
The draft of this PEP is not yet complete. I am shamelessly squatting on the PEP number which provides a cute relation to the original PEP 484. The draft will be completed in the upcoming days.
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. For this reason, its usability is lacking and some parts of typing necessarily feel like an afterthought.
This PEP addresses some of the major complaints of typing users, namely:
- the necessity for programmers to perform import book-keeping of names only used in static typing contexts;
- the surprising placement of runtime collections in the typing module (ABCs and NamedTuple);
- the surprising dichotomy between List and list, and so on;
- parts of static typing still performed at runtime (aliasing, cast, NewType, TypeVar).
The overarching goal of this PEP is to make static typing fully free of runtime side effects. In other words, no operations related to the process of annotating arguments, return values, and variables with types should generate runtime behavior which is otherwise useless at runtime.
This PEP is fully backwards compatible. Code written in previous ways might trigger some deprecations but will ultimately work as intended.
The newly described functionality requires Python 3.7 (for uses of the "annotations" future-import) or Python 3.8 (for refactorings of the typing module).
Tooling, including type checkers and linters, will have to be adapted to enable the new functionality.
Starting with Python 3.7, when from __future__ import annotations is used, function and variable annotations can specify generics directly on builtin types. Example:
from __future__ import annotations def find(haystack: dict[str, list[int]]) -> int: ...
This new way is preferred, the names List, Dict, FrozenSet, Set are deprecated. They won't be removed from the typing module for backwards compatibility but type checkers may warn about them in future versions when used in conjunction with the "annotations" future import.
Note: no runtime component is added to builtin collections to facilitate generics in any sense. This syntax is only supported in an annotation.
Starting with Python 3.7, when from __future__ import annotations is used, function and variable annotations can use special names from the typing module without the relevant explicit imports being present in the module.
from __future__ import annotations def loads( input: Union[str, bytes], *, encoding: Optional[str] = None ) -> dict[str, Any]: ...
All abstract base classes redefined in the typing module are being moved back to collections.abc including all additional functionality they gained in the typing module (in particular, generics support). The Generic type is also moved to collections.abc.
typing.NamedTuple is also moved to collections.
Aliases for all moved names will remain in the typing module for backwards compatibility. Using them directly becomes deprecated.
The usability issues described in the abstract are increasingly visible when a codebase adopts type hinting holistically. The need to jump between the type the programmer is just describing and imports needed to describe the type breaks the flow of thought. The need to import lookalike built-in collections for generics within annotations is a kludge which makes it harder to teach Python and looks inelegant. The remaining runtime component, even with use of the "annotations" future-import, impacts startup performance of annotated applications.