-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Protocols #3132
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
Protocols #3132
Changes from 75 commits
884481d
c19b6d4
83c1579
b01f5b0
16901fc
c8c1247
f430c65
beea7d2
260237a
5414eaf
7f8a514
92b71a2
83085c7
27b8570
41179c2
5171844
db123a3
31bf16c
03b5d72
65a8546
2768d76
93beb75
5135fd9
fdeb89b
1fbcb4a
d012e38
801770d
a625f37
fb6b1ad
adb68eb
b9a0b2d
969f76f
2e5de3e
1daaf8d
2b6d198
937c629
e8e6661
8e6306f
3f2d707
a82413d
957c76d
8cf08ec
e0083d9
c9629da
affec0c
01948dd
78a62eb
0ae409a
cb27279
5452227
798954a
9db4e51
7d2327a
88d4fed
d9187e2
3ee1c53
85082d5
2733e1a
232428f
4c04e0b
e81a3f9
b4ca6f0
b063767
79d8e30
480b977
483f163
70c3ae0
509113a
0cb0985
9f554b6
70463a5
78f2011
473a2d2
8dfe8ea
f7e55fa
06a1680
204ec82
985d1f7
8d2e199
f0471c1
4dfa3e1
6d05060
759409f
83501ff
166b6da
d25bcfc
d652a69
addac40
803ce1e
1c9f6f9
3c0411c
491b31f
98f0180
0f55718
513c759
36f3d6d
05b70ab
561856e
228f621
0716b59
9cd4e29
eb06c55
59aeed2
73d2d69
ee18dde
34d1cd1
fbbd169
3acd19e
22ad771
4f2391e
359a43b
eacff5f
afe1291
1858ed9
057a871
28c1b9d
ad2bcaa
8af7248
9ca98b2
0cb13b7
fd06408
969a64b
e9cbba8
e93f839
9292971
4c3f4e2
e71dff0
d195964
318bb70
3d8782f
6e1100c
5bfa3b2
82f01b7
c7304bd
58e6b36
eefe881
96a04ae
b1c4d37
8b6006c
f76349b
c3bfe2b
e2f4f5d
91fe9fd
aaea344
27c3e36
cfa539d
713db0c
40d635f
19073e5
50d98c0
9411255
f1c915e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -151,7 +151,236 @@ concrete. As with normal overrides, a dynamically typed method can | |
implement a statically typed abstract method defined in an abstract | ||
base class. | ||
|
||
.. _protocol-types: | ||
|
||
Protocols and structural subtyping | ||
********************************** | ||
|
||
.. note:: | ||
|
||
The support for structural subtyping is still experimental. Some features | ||
might be not yet implemented, mypy could pass unsafe code or reject | ||
working code. | ||
|
||
There are two main type systems with respect to subtyping: nominal subtyping | ||
and structural subtyping. The *nominal* subtyping is based on class hierarchy, | ||
so that if class ``D`` inherits from class ``C``, then it is a subtype | ||
of ``C``. This type system is primarily used in mypy since it allows | ||
to produce clear and concise error messages, and since Python provides native | ||
``isinstance()`` checks based on class hierarchy. The *structural* subtyping | ||
however has its own advantages. In this system class ``D`` is a subtype | ||
of class ``C`` if the former has all attributes of the latter with | ||
compatible types. For example: | ||
|
||
.. code-block:: python | ||
|
||
from typing import Sized | ||
|
||
def my_len(obj: Sized) -> int: | ||
... | ||
|
||
class MyCollection: | ||
... | ||
def __len__(self) -> int: | ||
return 42 | ||
|
||
my_len(MyCollection()) # OK, since 'MyCollection' is a subtype of 'Sized' | ||
|
||
This type system is a static equivalent of duck typing, well known by Python | ||
programmers. Mypy provides an opt-in support for structural subtyping via | ||
protocol classes described in this section. | ||
See `PEP 544 <https://www.python.org/dev/peps/pep-0544/>`_ for | ||
specification of protocols and structural subtyping in Python. | ||
|
||
User defined protocols | ||
********************** | ||
|
||
To define a protocol class, one must inherit the special ``typing.Protocol`` | ||
class: | ||
|
||
.. code-block:: python | ||
|
||
from typing import Protocol, Iterable | ||
|
||
class SupportsClose(Protocol): | ||
def close(self) -> None: | ||
... | ||
|
||
class Resource: # Note, this class does not have 'SupportsClose' base. | ||
# some methods | ||
def close(self) -> None: | ||
self.resource.release() | ||
|
||
def close_all(things: Iterable[SupportsClose]) -> None: | ||
for thing in things: | ||
thing.close() | ||
|
||
close_all([Resource(), open('some/file')]) # This passes type check | ||
|
||
Defining subprotocols | ||
********************* | ||
|
||
Subprotocols are also supported. Existing protocols can be extended | ||
and merged using multiple inheritance. For example: | ||
|
||
.. code-block:: python | ||
|
||
# continuing from previous example | ||
|
||
class SizedLabeledResource(SupportsClose, Sized, Protocol): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to what was discussed above, don't use |
||
label: str | ||
|
||
class AdvancedResource(Resource): | ||
def __init__(self, label: str) -> None: | ||
self.label = label | ||
def __len__(self) -> int: | ||
... | ||
|
||
resource: SizedLabeledResource | ||
|
||
# some code | ||
|
||
resource = AdvancedResource('handle with care') # OK | ||
|
||
Note that inheriting from existing protocols does not automatically turn | ||
a subclass into a protocol, it just creates a usual (non-protocol) ABC that | ||
implements given protocols. The ``typing.Protocol`` base must always be | ||
explicitly present: | ||
|
||
.. code-block:: python | ||
|
||
class NewProtocol(Sized): # This is NOT a protocol | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, avoid the use of |
||
new_attr: int | ||
|
||
class Concrete: | ||
new_attr: int | ||
def __len__(self) -> int: | ||
... | ||
|
||
x: NewProtocol = Concrete() # Error, nominal subtyping is used by default | ||
|
||
Generic protocols | ||
***************** | ||
|
||
Generic protocols are also supported, generic protocols mostly follow | ||
the normal rules for generic classes, the main difference is that declaring | ||
variance is not necessary for protocols, mypy can infer it. Examples: | ||
|
||
.. code-block:: python | ||
|
||
from typing import Protocol, TypeVar | ||
|
||
T = TypeVar('T') | ||
|
||
class Box(Protocol[T]): | ||
content: T | ||
|
||
def do_stuff(one: Box[str], other: Box[bytes]) -> None: | ||
... | ||
|
||
class StringWrapper: | ||
def __init__(self, content: str) -> None: | ||
self.content = content | ||
|
||
class BytesWrapper: | ||
def __init__(self, content: bytes) -> None: | ||
self.content = content | ||
|
||
do_stuff(StringWrapper('one'), BytesWrapper(b'other')) # OK | ||
|
||
x: Box[float] | ||
y: Box[int] | ||
x = y # This is also OK due to inferred covariance of the protocol 'Box'. | ||
|
||
See :ref:`generic-classes` for more details on generic classes and variance. | ||
|
||
Recursive protocols | ||
******************* | ||
|
||
Protocols (both generic and non-generic) can be recursive and mutually | ||
recursive. This could be useful for declaring abstract recursive collections | ||
such as trees and linked lists: | ||
|
||
.. code-block:: python | ||
|
||
from typing import Protocol, TypeVar, Optional | ||
|
||
class TreeLike(Protocol): | ||
value: int | ||
left: Optional['TreeLike'] | ||
right: Optional['TreeLike'] | ||
|
||
class SimpleTree: | ||
def __init__(self, value: int) -> None: | ||
self.value = value | ||
self.left = self.right = None | ||
|
||
root: TreeLike = SimpleTree(0) # OK | ||
|
||
T = TypeVar('T') | ||
class Linked(Protocol[T]): | ||
val: T | ||
next: 'Linked[T]' | ||
|
||
class L: | ||
val: int | ||
next: 'L' | ||
|
||
def last(seq: Linked[T]) -> T: | ||
... | ||
|
||
result = last(L()) # The inferred type of 'result' is 'int' | ||
|
||
Predefined protocols in ``typing`` module | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove this section for now. We will add this later once the typeshed PR has been merged. |
||
***************************************** | ||
|
||
Most ABCs in ``typing`` module are protocol classes describing | ||
common Python protocols such as ``Iterator``, ``Awaitable``, ``Mapping``, etc. | ||
(see `Python Docs <https://docs.python.org/3/library/typing.html>`_ | ||
for an exhaustive list) | ||
For example, the following class will be considered a subtype of | ||
``typing.Sized`` and ``typing.Iterable[int]``: | ||
|
||
.. code-block:: python | ||
|
||
from typing import Iterator, Iterable | ||
|
||
class Bucket: | ||
... | ||
def __len__(self) -> int: | ||
return 22 | ||
def __iter__(self) -> Iterator[int]: | ||
yield 22 | ||
|
||
def collect(items: Iterable[int]) -> int: ... | ||
result: int = collect(Bucket()) # Passes type check | ||
|
||
Using ``isinstance()`` with protocols | ||
************************************* | ||
|
||
To use a protocol class with ``isinstance()``, one needs to decorate it with | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again it might be better to |
||
a special ``typing.runtime`` decorator. It will add support for basic runtime | ||
structural checks: | ||
|
||
.. code-block:: python | ||
|
||
from typing import Protocol, runtime | ||
|
||
@runtime | ||
class Portable(Protocol): | ||
handles: int | ||
|
||
class Mug: | ||
def __init__(self) -> None: | ||
self.handles = 1 | ||
|
||
mug = Mug() | ||
if isinstance(mug, Portable): | ||
use(mug.handles) # Works statically and at runtime. | ||
|
||
.. note:: | ||
``isinstance()`` is with protocols not completely safe at runtime. | ||
For example, signatures of methods are not checked. The runtime | ||
implementation only checks the presence of all protocol members | ||
in object's MRO. | ||
|
||
There are also plans to support more Python-style "duck typing" in | ||
the type system. The details are still open. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -101,35 +101,39 @@ Is mypy free? | |
Yes. Mypy is free software, and it can also be used for commercial and | ||
proprietary projects. Mypy is available under the MIT license. | ||
|
||
Why not use structural subtyping? | ||
********************************* | ||
Can I use structural subtyping? | ||
******************************* | ||
|
||
Mypy primarily uses `nominal subtyping | ||
<https://en.wikipedia.org/wiki/Nominative_type_system>`_ instead of | ||
Mypy provides support for both `nominal subtyping | ||
<https://en.wikipedia.org/wiki/Nominative_type_system>`_ and | ||
`structural subtyping | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mention that structural subtyping is still experimental. |
||
<https://en.wikipedia.org/wiki/Structural_type_system>`_. Some argue | ||
that structural subtyping is better suited for languages with duck | ||
typing such as Python. | ||
|
||
Here are some reasons why mypy uses nominal subtyping: | ||
<https://en.wikipedia.org/wiki/Structural_type_system>`_. | ||
Support for structural subtyping is considered experimental. | ||
Some argue that structural subtyping is better suited for languages with duck | ||
typing such as Python. Mypy however primarily uses nominal subtyping, | ||
leaving structural subtyping opt-in. Here are some reasons why: | ||
|
||
1. It is easy to generate short and informative error messages when | ||
using a nominal type system. This is especially important when | ||
using type inference. | ||
|
||
2. Python supports basically nominal isinstance tests and they are | ||
widely used in programs. It is not clear how to support isinstance | ||
in a purely structural type system while remaining compatible with | ||
Python idioms. | ||
2. Python provides built-in support for nominal ``isinstance()`` tests and | ||
they are widely used in programs. Only limited support for structural | ||
``isinstance()`` exists for ABCs in ``collections.abc`` and ``typing`` | ||
standard library modules. | ||
|
||
3. Many programmers are already familiar with nominal subtyping and it | ||
has been successfully used in languages such as Java, C++ and | ||
C#. Only few languages use structural subtyping. | ||
|
||
However, structural subtyping can also be useful. Structural subtyping | ||
is a likely feature to be added to mypy in the future, even though we | ||
expect that most mypy programs will still primarily use nominal | ||
subtyping. | ||
However, structural subtyping can also be useful. For example, a "public API" | ||
will be more flexible and convenient for users if it is typed with protocols. | ||
Also, using protocol types removes the necessity to explicitly declare | ||
implementations of ABCs. Finally, protocol types may feel more natural for | ||
Python programmers. As a rule of thumb, one could prefer protocols for | ||
function argument types and normal classes for return types. For more details | ||
about protocol types and structural subtyping see :ref:`protocol-types` and | ||
`PEP 544 <https://www.python.org/dev/peps/pep-0544/>`_. | ||
|
||
I like Python and I have no need for static typing | ||
************************************************** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
Generics | ||
======== | ||
|
||
.. _generic-classes: | ||
|
||
Defining generic classes | ||
************************ | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This example won't be valid until we use protocols in
typing
, and I think that this will take a while after this PR has been merged. Let's only talk about user-defined protocols for now in mypy documentation. We'll add the documentation about protocols intyping
later once we've updated the typeshed stubs.Also add a note saying that many or all ABCs in
typing
will be updated to be protocols in the future somewhere in the documentation.