Skip to content

Commit 74d7f76

Browse files
authored
bpo-37058: PEP 544: Add Protocol to typing module (GH-13585)
I tried to get rid of the `_ProtocolMeta`, but unfortunately it didn'y work. My idea to return a generic alias from `@runtime_checkable` made runtime protocols unpickleable. I am not sure what is worse (a custom metaclass or having some classes unpickleable), so I decided to stick with the status quo (since there were no complains so far). So essentially this is a copy of the implementation in `typing_extensions` with two modifications: * Rename `@runtime` to `@runtime_checkable` (plus corresponding updates). * Allow protocols that extend `collections.abc.Iterable` etc.
1 parent 3880f26 commit 74d7f76

File tree

4 files changed

+1053
-119
lines changed

4 files changed

+1053
-119
lines changed

Doc/library/typing.rst

+97-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717

1818
--------------
1919

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

396+
397+
Nominal vs structural subtyping
398+
-------------------------------
399+
400+
Initially :pep:`484` defined Python static type system as using
401+
*nominal subtyping*. This means that a class ``A`` is allowed where
402+
a class ``B`` is expected if and only if ``A`` is a subclass of ``B``.
403+
404+
This requirement previously also applied to abstract base classes, such as
405+
:class:`Iterable`. The problem with this approach is that a class had
406+
to be explicitly marked to support them, which is unpythonic and unlike
407+
what one would normally do in idiomatic dynamically typed Python code.
408+
For example, this conforms to the :pep:`484`::
409+
410+
from typing import Sized, Iterable, Iterator
411+
412+
class Bucket(Sized, Iterable[int]):
413+
...
414+
def __len__(self) -> int: ...
415+
def __iter__(self) -> Iterator[int]: ...
416+
417+
:pep:`544` allows to solve this problem by allowing users to write
418+
the above code without explicit base classes in the class definition,
419+
allowing ``Bucket`` to be implicitly considered a subtype of both ``Sized``
420+
and ``Iterable[int]`` by static type checkers. This is known as
421+
*structural subtyping* (or static duck-typing)::
422+
423+
from typing import Iterator, Iterable
424+
425+
class Bucket: # Note: no base classes
426+
...
427+
def __len__(self) -> int: ...
428+
def __iter__(self) -> Iterator[int]: ...
429+
430+
def collect(items: Iterable[int]) -> int: ...
431+
result = collect(Bucket()) # Passes type check
432+
433+
Moreover, by subclassing a special class :class:`Protocol`, a user
434+
can define new custom protocols to fully enjoy structural subtyping
435+
(see examples below).
436+
437+
395438
Classes, functions, and decorators
396439
----------------------------------
397440

@@ -459,6 +502,39 @@ The module defines the following classes, functions and decorators:
459502
except KeyError:
460503
return default
461504

505+
.. class:: Protocol(Generic)
506+
507+
Base class for protocol classes. Protocol classes are defined like this::
508+
509+
class Proto(Protocol):
510+
def meth(self) -> int:
511+
...
512+
513+
Such classes are primarily used with static type checkers that recognize
514+
structural subtyping (static duck-typing), for example::
515+
516+
class C:
517+
def meth(self) -> int:
518+
return 0
519+
520+
def func(x: Proto) -> int:
521+
return x.meth()
522+
523+
func(C()) # Passes static type check
524+
525+
See :pep:`544` for details. Protocol classes decorated with
526+
:func:`runtime_checkable` (described later) act as simple-minded runtime
527+
protocols that check only the presence of given attributes, ignoring their
528+
type signatures.
529+
530+
Protocol classes can be generic, for example::
531+
532+
class GenProto(Protocol[T]):
533+
def meth(self) -> T:
534+
...
535+
536+
.. versionadded:: 3.8
537+
462538
.. class:: Type(Generic[CT_co])
463539

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

1112+
.. decorator:: runtime_checkable
1113+
1114+
Mark a protocol class as a runtime protocol.
1115+
1116+
Such a protocol can be used with :func:`isinstance` and :func:`issubclass`.
1117+
This raises :exc:`TypeError` when applied to a non-protocol class. This
1118+
allows a simple-minded structural check, very similar to "one trick ponies"
1119+
in :mod:`collections.abc` such as :class:`Iterable`. For example::
1120+
1121+
@runtime_checkable
1122+
class Closable(Protocol):
1123+
def close(self): ...
1124+
1125+
assert isinstance(open('/some/file'), Closable)
1126+
1127+
**Warning:** this will check only the presence of the required methods,
1128+
not their type signatures!
1129+
1130+
.. versionadded:: 3.8
1131+
10361132
.. data:: Any
10371133

10381134
Special type indicating an unconstrained type.

0 commit comments

Comments
 (0)