Skip to content

bpo-37058: PEP 544: Add Protocol to typing module #13585

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 28, 2019
98 changes: 97 additions & 1 deletion Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@

--------------

This module supports type hints as specified by :pep:`484` and :pep:`526`.
This module provides runtime support for type hints as specified by
:pep:`484`, :pep:`526`, :pep:`544`, :pep:`586`, :pep:`589`, and :pep:`591`.
The most fundamental support consists of the types :data:`Any`, :data:`Union`,
:data:`Tuple`, :data:`Callable`, :class:`TypeVar`, and
:class:`Generic`. For full specification please see :pep:`484`. For
Expand Down Expand Up @@ -392,6 +393,48 @@ it as a return value) of a more specialized type is a type error. For example::
Use :class:`object` to indicate that a value could be any type in a typesafe
manner. Use :data:`Any` to indicate that a value is dynamically typed.


Nominal vs structural subtyping
-------------------------------

Initially :pep:`484` defined Python static type system as using
*nominal subtyping*. This means that a class ``A`` is allowed where
a class ``B`` is expected if and only if ``A`` is a subclass of ``B``.

This requirement previously also applied to abstract base classes, such as
:class:`Iterable`. The problem with this approach is that a class had
to be explicitly marked to support them, which is unpythonic and unlike
what one would normally do in idiomatic dynamically typed Python code.
For example, this conforms to the :pep:`484`::

from typing import Sized, Iterable, Iterator

class Bucket(Sized, Iterable[int]):
...
def __len__(self) -> int: ...
def __iter__(self) -> Iterator[int]: ...

:pep:`544` allows to solve this problem by allowing users to write
the above code without explicit base classes in the class definition,
allowing ``Bucket`` to be implicitly considered a subtype of both ``Sized``
and ``Iterable[int]`` by static type checkers. This is known as
*structural subtyping* (or static duck-typing)::

from typing import Iterator, Iterable

class Bucket: # Note: no base classes
...
def __len__(self) -> int: ...
def __iter__(self) -> Iterator[int]: ...

def collect(items: Iterable[int]) -> int: ...
result = collect(Bucket()) # Passes type check

Moreover, by subclassing a special class :class:`Protocol`, a user
can define new custom protocols to fully enjoy structural subtyping
(see examples below).


Classes, functions, and decorators
----------------------------------

Expand Down Expand Up @@ -459,6 +502,39 @@ The module defines the following classes, functions and decorators:
except KeyError:
return default

.. class:: Protocol(Generic)

Base class for protocol classes. Protocol classes are defined like this::

class Proto(Protocol):
def meth(self) -> int:
...

Such classes are primarily used with static type checkers that recognize
structural subtyping (static duck-typing), for example::

class C:
def meth(self) -> int:
return 0

def func(x: Proto) -> int:
return x.meth()

func(C()) # Passes static type check

See :pep:`544` for details. Protocol classes decorated with
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is okay for now, but at some point we may want to make it so that the docs are self-contained without any references to PEPs. (This is so that in the future the implementation can evolve, and the docs should describe the current implementation, while the PEP describes the implementation as it was at the time the PEP was finalized.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we can expand the docs later.

:func:`runtime_checkable` (described later) act as simple-minded runtime
protocols that check only the presence of given attributes, ignoring their
type signatures.

Protocol classes can be generic, for example::

class GenProto(Protocol[T]):
def meth(self) -> T:
...

.. versionadded:: 3.8

.. class:: Type(Generic[CT_co])

A variable annotated with ``C`` may accept a value of type ``C``. In
Expand Down Expand Up @@ -1033,6 +1109,26 @@ The module defines the following classes, functions and decorators:
Note that returning instances of private classes is not recommended.
It is usually preferable to make such classes public.

.. decorator:: runtime_checkable

Mark a protocol class as a runtime protocol.

Such a protocol can be used with :func:`isinstance` and :func:`issubclass`.
This raises :exc:`TypeError` when applied to a non-protocol class. This
allows a simple-minded structural check, very similar to "one trick ponies"
in :mod:`collections.abc` such as :class:`Iterable`. For example::

@runtime_checkable
class Closable(Protocol):
def close(self): ...

assert isinstance(open('/some/file'), Closable)

**Warning:** this will check only the presence of the required methods,
not their type signatures!

.. versionadded:: 3.8

.. data:: Any

Special type indicating an unconstrained type.
Expand Down
Loading