603 lines
22 KiB
ReStructuredText
603 lines
22 KiB
ReStructuredText
.. _libraries:
|
||
|
||
***********************
|
||
Typing Python Libraries
|
||
***********************
|
||
|
||
Much of Python’s popularity can be attributed to the rich collection of
|
||
Python libraries available to developers. Authors of these libraries
|
||
play an important role in improving the experience for Python
|
||
developers. This document provides some recommendations and guidance for
|
||
Python library authors.
|
||
|
||
These recommendations are intended to provide the following benefits:
|
||
|
||
1. Consumers of libraries should have a great coding experience with
|
||
fast and accurate completion suggestions, class and function
|
||
documentation, signature help (including parameter default values),
|
||
hover text, and auto-imports. This should happen by default without
|
||
needing to download extra packages and without any special
|
||
configuration. These features should be consistent across the Python
|
||
ecosystem regardless of a developer’s choice of editor, IDE, notebook
|
||
environment, etc.
|
||
2. Consumers of libraries should be able to rely on complete and
|
||
accurate type information so static type checkers can detect and
|
||
report type inconsistencies and other violations of the interface
|
||
contract.
|
||
3. Library authors should be able to specify a well-defined interface
|
||
contract that is enforced by tools. This allows a library
|
||
implementation to evolve and improve without breaking consumers of
|
||
the library.
|
||
4. Library authors should have the benefits of static type checking to
|
||
produce high-quality, bug-free implementations.
|
||
|
||
Inlined Type Annotations and Type Stubs
|
||
=======================================
|
||
|
||
`PEP 561 <https://www.python.org/dev/peps/pep-0561/>`__ documents
|
||
several ways type information can be delivered for a library: inlined
|
||
type annotations, type stub files included in the package, a separate
|
||
companion type stub package, and type stubs in the typeshed repository.
|
||
Some of these options fall short on delivering the benefits above. We
|
||
therefore provide the following more specific guidance to library
|
||
authors.
|
||
|
||
.. note::
|
||
All libraries should include inlined type annotations for the
|
||
functions, classes, methods, and constants that comprise the public
|
||
interface for the library.
|
||
|
||
Inlined type annotations should be included directly within the source
|
||
code that ships with the package. Of the options listed in PEP 561,
|
||
inlined type annotations offer the most benefits. They typically require
|
||
the least effort to add and maintain, they are always consistent with
|
||
the implementation, and docstrings and default parameter values are
|
||
readily available, allowing language servers to enhance the development
|
||
experience.
|
||
|
||
There are cases where inlined type annotations are not possible — most
|
||
notably when a library’s exposed functionality is implemented in a
|
||
language other than Python.
|
||
|
||
.. note::
|
||
Libraries that expose symbols implemented in languages other than
|
||
Python should include stub (``.pyi``) files that describe the types for
|
||
those symbols. These stubs should also contain docstrings and default
|
||
parameter values.
|
||
|
||
In many existing type stubs (such as those found in typeshed), default
|
||
parameter values are replaced with with ``...`` and all docstrings are
|
||
removed. We recommend that default values and docstrings remain within
|
||
the type stub file so language servers can display this information to
|
||
developers.
|
||
|
||
Library Interface
|
||
=================
|
||
|
||
`PEP 561 <https://www.python.org/dev/peps/pep-0561/>`__ indicates that a
|
||
``py.typed`` marker file must be included in the package if the author
|
||
wishes to support type checking of their code.
|
||
|
||
If a ``py.typed`` module is present, a type checker will treat all modules
|
||
within that package (i.e. all files that end in ``.py`` or ``.pyi``) as
|
||
importable unless the file name begins with an underscore. These modules
|
||
comprise the supported interface for the library.
|
||
|
||
Each module exposes a set of symbols. Some of these symbols are
|
||
considered "private” — implementation details that are not part of the
|
||
library’s interface. Type checkers can use the following rules
|
||
to determine which symbols are visible outside of the package.
|
||
|
||
- Symbols whose names begin with an underscore (but are not dunder
|
||
names) are considered private.
|
||
- Imported symbols are considered private by default. If they use the
|
||
``import A as A`` (a redundant module alias), ``from X import A as A`` (a
|
||
redundant symbol alias), or ``from . import A`` forms, symbol ``A`` is
|
||
not private unless the name begins with an underscore. If a file
|
||
``__init__.py`` uses form ``from .A import X``, symbol ``A`` is treated
|
||
likewise. If a wildcard import (of the form ``from X import *``) is
|
||
used, all symbols referenced by the wildcard are not private.
|
||
- A module can expose an ``__all__`` symbol at the module level that
|
||
provides a list of names that are considered part of the interface.
|
||
This overrides all other rules above, allowing imported symbols or
|
||
symbols whose names begin with an underscore to be included in the
|
||
interface.
|
||
- Local variables within a function (including nested functions) are
|
||
always considered private.
|
||
|
||
The following idioms are supported for defining the values contained
|
||
within ``__all__``. These restrictions allow type checkers to statically
|
||
determine the value of ``__all__``.
|
||
|
||
- ``__all__ = ('a', b')``
|
||
- ``__all__ = ['a', b']``
|
||
- ``__all__ += ['a', b']``
|
||
- ``__all__ += submodule.__all__``
|
||
- ``__all__.extend(['a', b'])``
|
||
- ``__all__.extend(submodule.__all__)``
|
||
- ``__all__.append('a')``
|
||
- ``__all__.remove('a')``
|
||
|
||
Type Completeness
|
||
=================
|
||
|
||
A “py.typed” library should aim to be type complete so that type
|
||
checking and inspection can work to their full extent. Here we say that a
|
||
library is “type complete” if all of the symbols
|
||
that comprise its interface have type annotations that refer to types
|
||
that are fully known. Private symbols are exempt.
|
||
|
||
The following are best practice recommendations for how to define “type complete”:
|
||
|
||
Classes:
|
||
|
||
- All class variables, instance variables, and methods that are
|
||
“visible” (not overridden) are annotated and refer to known types
|
||
- If a class is a subclass of a generic class, type arguments are
|
||
provided for each generic type parameter, and these type arguments
|
||
are known types
|
||
|
||
Functions and Methods:
|
||
|
||
- All input parameters have type annotations that refer to known types
|
||
- The return parameter is annotated and refers to a known type
|
||
- The result of applying one or more decorators results in a known type
|
||
|
||
Type Aliases:
|
||
|
||
- All of the types referenced by the type alias are known
|
||
|
||
Variables:
|
||
|
||
- All variables have type annotations that refer to known types
|
||
|
||
Type annotations can be omitted in a few specific cases where the type
|
||
is obvious from the context:
|
||
|
||
- Constants that are assigned simple literal values
|
||
(e.g. ``RED = '#F00'`` or ``MAX_TIMEOUT = 50`` or
|
||
``room_temperature: Final = 20``). A constant is a symbol that is
|
||
assigned only once and is either annotated with ``Final`` or is named
|
||
in all-caps. A constant that is not assigned a simple literal value
|
||
requires explicit annotations, preferably with a ``Final`` annotation
|
||
(e.g. ``WOODWINDS: Final[List[str]] = ['Oboe', 'Bassoon']``).
|
||
- Enum values within an Enum class do not require annotations because
|
||
they take on the type of the Enum class.
|
||
- Type aliases do not require annotations. A type alias is a symbol
|
||
that is defined at a module level with a single assignment where the
|
||
assigned value is an instantiable type, as opposed to a class
|
||
instance
|
||
(e.g. ``Foo = Callable[[Literal["a", "b"]], Union[int, str]]`` or
|
||
``Bar = Optional[MyGenericClass[int]]``).
|
||
- The “self” parameter in an instance method and the “cls” parameter in
|
||
a class method do not require an explicit annotation.
|
||
- The return type for an ``__init__`` method does not need to be
|
||
specified, since it is always ``None``.
|
||
- The following module-level symbols do not require type annotations:
|
||
``__all__``,\ ``__author__``, ``__copyright__``, ``__email__``,
|
||
``__license__``, ``__title__``, ``__uri__``, ``__version__``.
|
||
- The following class-level symbols do not require type annotations:
|
||
``__class__``, ``__dict__``, ``__doc__``, ``__module__``,
|
||
``__slots__``.
|
||
|
||
Examples of known and unknown types
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
.. code:: python
|
||
|
||
|
||
# Variable with unknown type
|
||
a = [3, 4, 5]
|
||
|
||
# Variable with known type
|
||
a: List[int] = [3, 4, 5]
|
||
|
||
# Type alias with partially unknown type (because type
|
||
# arguments are missing for list and dict)
|
||
DictOrList = Union[list, dict]
|
||
|
||
# Type alias with known type
|
||
DictOrList = Union[List[Any], Dict[str, Any]]
|
||
|
||
# Generic type alias with known type
|
||
_T = TypeVar("_T")
|
||
DictOrList = Union[List[_T], Dict[str, _T]]
|
||
|
||
# Function with known type
|
||
def func(a: Optional[int], b: Dict[str, float] = {}) -> None:
|
||
pass
|
||
|
||
# Function with partially unknown type (because type annotations
|
||
# are missing for input parameters and return type)
|
||
def func(a, b):
|
||
pass
|
||
|
||
# Function with partially unknown type (because of missing
|
||
# type args on Dict)
|
||
def func(a: int, b: Dict) -> None:
|
||
pass
|
||
|
||
# Function with partially unknown type (because return type
|
||
# annotation is missing)
|
||
def func(a: int, b: Dict[str, float]):
|
||
pass
|
||
|
||
# Decorator with partially unknown type (because type annotations
|
||
# are missing for input parameters and return type)
|
||
def my_decorator(func):
|
||
return func
|
||
|
||
# Function with partially unknown type (because type is obscured
|
||
# by untyped decorator)
|
||
@my_decorator
|
||
def func(a: int) -> str:
|
||
pass
|
||
|
||
|
||
# Class with known type
|
||
class MyClass:
|
||
height: float = 2.0
|
||
|
||
def __init__(self, name: str, age: int):
|
||
self.age: int = age
|
||
|
||
@property
|
||
def name(self) -> str:
|
||
...
|
||
|
||
# Class with partially unknown type
|
||
class MyClass:
|
||
# Missing type annotation for class variable
|
||
height = 2.0
|
||
|
||
# Missing input parameter annotations
|
||
def __init__(self, name, age):
|
||
# Missing type annotation for instance variable
|
||
self.age = age
|
||
|
||
# Missing return type annotation
|
||
@property
|
||
def name(self):
|
||
...
|
||
|
||
# Class with partially unknown type
|
||
class BaseClass:
|
||
# Missing type annotation
|
||
height = 2.0
|
||
|
||
# Missing type annotation
|
||
def get_stuff(self):
|
||
...
|
||
|
||
# Class with known type (because it overrides all symbols
|
||
# exposed by BaseClass that have incomplete types)
|
||
class DerivedClass(BaseClass):
|
||
height: float
|
||
|
||
def get_stuff(self) -> str:
|
||
...
|
||
|
||
# Class with partially unknown type because base class
|
||
# (dict) is generic, and type arguments are not specified.
|
||
class DictSubclass(dict):
|
||
pass
|
||
|
||
Best Practices for Inlined Types
|
||
================================
|
||
|
||
Wide vs. Narrow Types
|
||
~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
In type theory, when comparing two types that are related to each other,
|
||
the “wider” type is the one that is more general, and the “narrower”
|
||
type is more specific. For example, ``Sequence[str]`` is a wider type
|
||
than ``List[str]`` because all ``List`` objects are also ``Sequence``
|
||
objects, but the converse is not true. A subclass is narrower than a
|
||
class it derives from. A union of types is wider than the individual
|
||
types that comprise the union.
|
||
|
||
In general, a function input parameter should be annotated with the
|
||
widest possible type supported by the implementation. For example, if
|
||
the implementation requires the caller to provide an iterable collection
|
||
of strings, the parameter should be annotated as ``Iterable[str]``, not
|
||
as ``List[str]``. The latter type is narrower than necessary, so if a
|
||
user attempts to pass a tuple of strings (which is supported by the
|
||
implementation), a type checker will complain about a type
|
||
incompatibility.
|
||
|
||
As a specific application of the “use the widest type possible” rule,
|
||
libraries should generally use immutable forms of container types
|
||
instead of mutable forms (unless the function needs to modify the
|
||
container). Use ``Sequence`` rather than ``List``, ``Mapping`` rather
|
||
than ``Dict``, etc. Immutable containers allow for more flexibility
|
||
because their type parameters are covariant rather than invariant. A
|
||
parameter that is typed as ``Sequence[Union[str, int]]`` can accept a
|
||
``List[int]``, ``Sequence[str]``, and a ``Sequence[int]``. But a
|
||
parameter typed as ``List[Union[str, int]]`` is much more restrictive
|
||
and accepts only a ``List[Union[str, int]]``.
|
||
|
||
Overloads
|
||
~~~~~~~~~
|
||
|
||
If a function or method can return multiple different types and those
|
||
types can be determined based on the presence or types of certain
|
||
parameters, use the ``@overload`` mechanism defined in `PEP
|
||
484 <https://www.python.org/dev/peps/pep-0484/#id45>`__. When overloads
|
||
are used within a “.py” file, they must appear prior to the function
|
||
implementation, which should not have an ``@overload`` decorator.
|
||
|
||
Keyword-only Parameters
|
||
~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
If a function or method is intended to take parameters that are
|
||
specified only by name, use the keyword-only separator (``*``).
|
||
|
||
.. code:: python
|
||
|
||
def create_user(age: int, *, dob: Optional[date] = None):
|
||
...
|
||
|
||
Annotating Decorators
|
||
~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
Decorators modify the behavior of a class or a function. Providing
|
||
annotations for decorators is straightforward if the decorator retains
|
||
the original signature of the decorated function.
|
||
|
||
.. code:: python
|
||
|
||
_F = TypeVar("_F", bound=Callable[..., Any])
|
||
|
||
def simple_decorator(_func: _F) -> _F:
|
||
"""
|
||
Simple decorators are invoked without parentheses like this:
|
||
@simple_decorator
|
||
def my_function(): ...
|
||
"""
|
||
...
|
||
|
||
def complex_decorator(*, mode: str) -> Callable[[_F], _F]:
|
||
"""
|
||
Complex decorators are invoked with arguments like this:
|
||
@complex_decorator(mode="easy")
|
||
def my_function(): ...
|
||
"""
|
||
...
|
||
|
||
Decorators that mutate the signature of the decorated function present
|
||
challenges for type annotations. The ``ParamSpec`` and ``Concatenate``
|
||
mechanisms described in `PEP
|
||
612 <https://www.python.org/dev/peps/pep-0612/>`__ provide some help
|
||
here, but these are available only in Python 3.10 and newer. More
|
||
complex signature mutations may require type annotations that erase the
|
||
original signature, thus blinding type checkers and other tools that
|
||
provide signature assistance. As such, library authors are discouraged
|
||
from creating decorators that mutate function signatures in this manner.
|
||
|
||
Generic Classes and Functions
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
Classes and functions that can operate in a generic manner on various
|
||
types should declare themselves as generic using the mechanisms
|
||
described in `PEP 484 <https://www.python.org/dev/peps/pep-0484/>`__.
|
||
This includes the use of ``TypeVar`` symbols. Typically, a ``TypeVar``
|
||
should be private to the file that declares it, and should therefore
|
||
begin with an underscore.
|
||
|
||
Type Aliases
|
||
~~~~~~~~~~~~
|
||
|
||
Type aliases are symbols that refer to other types. Generic type aliases
|
||
(those that refer to unspecialized generic classes) are supported by
|
||
most type checkers.
|
||
|
||
`PEP 613 <https://www.python.org/dev/peps/pep-0613/>`__ provides a way
|
||
to explicitly designate a symbol as a type alias using the new TypeAlias
|
||
annotation.
|
||
|
||
.. code:: python
|
||
|
||
# Simple type alias
|
||
FamilyPet = Union[Cat, Dog, GoldFish]
|
||
|
||
# Generic type alias
|
||
ListOrTuple = Union[List[_T], Tuple[_T, ...]]
|
||
|
||
# Recursive type alias
|
||
TreeNode = Union[LeafNode, List["TreeNode"]]
|
||
|
||
# Explicit type alias using PEP 613 syntax
|
||
StrOrInt: TypeAlias = Union[str, int]
|
||
|
||
Abstract Classes and Methods
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
Classes that must be subclassed should derive from ``ABC``, and methods
|
||
or properties that must be overridden should be decorated with the
|
||
``@abstractmethod`` decorator. This allows type checkers to validate
|
||
that the required methods have been overridden and provide developers
|
||
with useful error messages when they are not. It is customary to
|
||
implement an abstract method by raising a ``NotImplementedError``
|
||
exception.
|
||
|
||
.. code:: python
|
||
|
||
from abc import ABC, abstractmethod
|
||
|
||
class Hashable(ABC):
|
||
@property
|
||
@abstractmethod
|
||
def hash_value(self) -> int:
|
||
"""Subclasses must override"""
|
||
raise NotImplementedError()
|
||
|
||
@abstractmethod
|
||
def print(self) -> str:
|
||
"""Subclasses must override"""
|
||
raise NotImplementedError()
|
||
|
||
Final Classes and Methods
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
Classes that are not intended to be subclassed should be decorated as
|
||
``@final`` as described in `PEP
|
||
591 <https://www.python.org/dev/peps/pep-0591/>`__. The same decorator
|
||
can also be used to specify methods that cannot be overridden by
|
||
subclasses.
|
||
|
||
Literals
|
||
~~~~~~~~
|
||
|
||
Type annotations should make use of the Literal type where appropriate,
|
||
as described in `PEP 586 <https://www.python.org/dev/peps/pep-0586/>`__.
|
||
Literals allow for more type specificity than their non-literal
|
||
counterparts.
|
||
|
||
Constants
|
||
~~~~~~~~~
|
||
|
||
Constant values (those that are read-only) can be specified using the
|
||
Final annotation as described in `PEP
|
||
591 <https://www.python.org/dev/peps/pep-0591/>`__.
|
||
|
||
Type checkers will also typically treat variables that are named using
|
||
all upper-case characters as constants.
|
||
|
||
In both cases, it is OK to omit the declared type of a constant if it is
|
||
assigned a literal str, int, float, bool or None value. In such cases,
|
||
the type inference rules are clear and unambiguous, and adding a literal
|
||
type annotation would be redundant.
|
||
|
||
.. code:: python
|
||
|
||
# All-caps constant with inferred type
|
||
COLOR_FORMAT_RGB = "rgb"
|
||
|
||
# All-caps constant with explicit type
|
||
COLOR_FORMAT_RGB: Literal["rgb"] = "rgb"
|
||
LATEST_VERSION: Tuple[int, int] = (4, 5)
|
||
|
||
# Final variable with inferred type
|
||
ColorFormatRgb: Final = "rgb"
|
||
|
||
# Final variable with explicit type
|
||
ColorFormatRgb: Final[Literal["rgb"]] = "rgb"
|
||
LATEST_VERSION: Final[Tuple[int, int]] = (4, 5)
|
||
|
||
Typed Dictionaries, Data Classes, and Named Tuples
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
If your library runs only on newer versions of Python, you are
|
||
encouraged to use some of the new type-friendly classes.
|
||
|
||
NamedTuple (described in `PEP
|
||
484 <https://www.python.org/dev/peps/pep-0484/>`__) is preferred over
|
||
namedtuple.
|
||
|
||
Data classes (described in `PEP
|
||
557 <https://www.python.org/dev/peps/pep-0557/>`__) is preferred over
|
||
untyped dictionaries.
|
||
|
||
TypedDict (described in `PEP
|
||
589 <https://www.python.org/dev/peps/pep-0589/>`__) is preferred over
|
||
untyped dictionaries.
|
||
|
||
Compatibility with Older Python Versions
|
||
========================================
|
||
|
||
Each new version of Python from 3.5 onward has introduced new typing
|
||
constructs. This presents a challenge for library authors who want to
|
||
maintain runtime compatibility with older versions of Python. This
|
||
section documents several techniques that can be used to add types while
|
||
maintaining backward compatibility.
|
||
|
||
Quoted Annotations
|
||
~~~~~~~~~~~~~~~~~~
|
||
|
||
Type annotations for variables, parameters, and return types can be
|
||
placed in quotes. The Python interpreter will then ignore them, whereas
|
||
a type checker will interpret them as type annotations.
|
||
|
||
.. code:: python
|
||
|
||
# Older versions of Python do not support subscripting
|
||
# for the OrderedDict type, so the annotation must be
|
||
# enclosed in quotes.
|
||
def get_config(self) -> "OrderedDict[str, str]":
|
||
return self._config
|
||
|
||
Type Comment Annotations
|
||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
Python 3.0 introduced syntax for parameter and return type annotations,
|
||
as specified in `PEP 484 <https://www.python.org/dev/peps/pep-0484/>`__.
|
||
Python 3.6 introduced support for variable type annotations, as
|
||
specified in `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`__.
|
||
|
||
If you need to support older versions of Python, type annotations can
|
||
still be provided as “type comments”. These comments take the form #
|
||
type: .
|
||
|
||
.. code:: python
|
||
|
||
class Foo:
|
||
# Variable type comments go at the end of the line
|
||
# where the variable is assigned.
|
||
timeout = None # type: Optional[int]
|
||
|
||
# Function type comments can be specified on the
|
||
# line after the function signature.
|
||
def send_message(self, name, length):
|
||
# type: (str, int) -> None
|
||
...
|
||
|
||
# Function type comments can also specify the type
|
||
# of each parameter on its own line.
|
||
def receive_message(
|
||
self,
|
||
name, # type: str
|
||
length # type: int
|
||
):
|
||
# type: () -> Message
|
||
...
|
||
|
||
typing_extensions
|
||
~~~~~~~~~~~~~~~~~
|
||
|
||
New type features that require runtime support are typically included in
|
||
the stdlib ``typing`` module. Where possible, these new features are
|
||
back-ported to a runtime library called ``typing_extensions`` that works
|
||
with older Python runtimes.
|
||
|
||
TYPE_CHECKING
|
||
~~~~~~~~~~~~~
|
||
|
||
The ``typing`` module exposes a variable called ``TYPE_CHECKING`` which
|
||
has a value of False within the Python runtime but a value of True when
|
||
the type checker is performing its analysis. This allows type checking
|
||
statements to be conditionalized.
|
||
|
||
Care should be taken when using ``TYPE_CHECKING`` because behavioral
|
||
changes between type checking and runtime could mask problems that the
|
||
type checker would otherwise catch.
|
||
|
||
Non-Standard Type Behaviors
|
||
===========================
|
||
|
||
Type annotations provide a way to annotate typical type behaviors, but
|
||
some classes implement specialized, non-standard behaviors that cannot
|
||
be described using standard type annotations. For now, such types need
|
||
to be annotated as Any, which is unfortunate because the benefits of
|
||
static typing are lost.
|
||
|
||
Docstrings
|
||
==========
|
||
|
||
Docstrings should be provided for all classes, functions, and methods in
|
||
the interface. They should be formatted according to `PEP
|
||
257 <https://www.python.org/dev/peps/pep-0257/>`__.
|
||
|
||
There is currently no single agreed-upon standard for function and
|
||
method docstrings, but several common variants have emerged. We
|
||
recommend using one of these variants.
|