Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
3fa3e48
Collect various ideas
ilevkivskyi Mar 5, 2017
51b0ca0
Some formatting and reordering
ilevkivskyi Mar 5, 2017
993f7b3
Add some examples
ilevkivskyi Mar 5, 2017
e384336
Add planned link templates
ilevkivskyi Mar 6, 2017
7ea5d41
Add links + minor changes
Mar 6, 2017
cdcf62f
Polishing rationale
Mar 6, 2017
1ffed9b
Some more reshuffling and formatting
Mar 6, 2017
72ceae6
Add more examples
Mar 6, 2017
6bea2e8
Add more examples to existing approaches
Mar 6, 2017
d5972c3
Typos, reordering, and few more details (backport)
ilevkivskyi Mar 6, 2017
57d375f
Update list of protocols in typing
ilevkivskyi Mar 6, 2017
9d4d685
Defining protocols plus minor changes and formatting
ilevkivskyi Mar 7, 2017
82258d5
Explicitly declaring implementation and other changes
ilevkivskyi Mar 7, 2017
5d9fb7c
More polishing
ilevkivskyi Mar 7, 2017
a6e6d9e
Edit rejected/postponed ideas
ilevkivskyi Mar 7, 2017
3175013
Runtime things, reorder links
ilevkivskyi Mar 7, 2017
cbff669
Runtime decorator
ilevkivskyi Mar 7, 2017
dfccd06
Backward compatible part and last bits
Mar 8, 2017
60f4d52
Some clarifications
ilevkivskyi Mar 9, 2017
60e7f7f
Add links in text
ilevkivskyi Mar 9, 2017
c90aa1c
Caption style, add cross-refs
Mar 9, 2017
b008de1
Remove redundant links; + minor changes
ilevkivskyi Mar 10, 2017
02cca5c
One more tiny change
ilevkivskyi Mar 10, 2017
7d89b6b
Merge remote-tracking branch 'upstream/master' into protocols
ilevkivskyi Mar 10, 2017
0f3732a
Copyediting changes
JelleZijlstra Mar 10, 2017
95fbf58
Merge pull request #1 from JelleZijlstra/patch-2
ilevkivskyi Mar 10, 2017
cb65bff
Rename PEP with a valid number to get the build running
ilevkivskyi Mar 10, 2017
817bf2f
Reflow to 79 characters
ilevkivskyi Mar 10, 2017
2d89ba9
fix typo
JelleZijlstra Mar 10, 2017
0efcbff
Some grammar tweaks
brettcannon Mar 10, 2017
ebd4b17
Merge pull request #3 from brettcannon/patch-1
ilevkivskyi Mar 10, 2017
0de36be
Implement Guido's idea of EIBTI plus minor comments
ilevkivskyi Mar 11, 2017
767c58b
Fix typo
ilevkivskyi Mar 11, 2017
efc3154
Make implementation enforcement optional; fix order of Protocolbase
ilevkivskyi Mar 12, 2017
7d714c3
Add missing @abstractmethod decorators
ilevkivskyi Mar 13, 2017
d4ab050
Minor clarification
ilevkivskyi Mar 13, 2017
d9d21c2
Implement Jukka's and David's comments; few more minor things
ilevkivskyi Mar 16, 2017
4dfbfb2
Implement most comments by Łukasz; few more to do
ilevkivskyi Mar 17, 2017
d51420e
More changes in response to comments
Mar 18, 2017
f6240c8
Remove one reamining 'All'
ilevkivskyi Mar 18, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
More polishing
  • Loading branch information
ilevkivskyi committed Mar 7, 2017
commit 5d9fb7cfe0a4b6a74d1d390616142775d484c669
138 changes: 88 additions & 50 deletions pep-05xx.txt
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ non-protocol member. Examples::

class Template(Protocol):
name: str # This is a protocol member
value: int = 0 # This one too
value: int = 0 # This one too (with default)
__private: bool # This is NOT a protocol member

def method(self) -> None:
Expand All @@ -373,57 +373,82 @@ Explicitly declaring implementation
To explicitly declare that a certain class implements a given protocols, they
can be used as regular base classes. In this case:

* A protocol class could define default implementations for some methods
* A class could use implementations of some methods in protocols, both
protocol members with default implementations and non-protocol members
(``typing.Sequence`` is an example with useful default methods).
In particular, a protocol could provide a default implementation
of ``__init__`` that could be used in explicit subclasses.
In particular, a protocol could provide a default implementation of
``__init__`` that could be used in explicit subclasses.
The default implementations will not be used if the subtype relationship
is implicit and only via structural subtyping -- the semantics of
inheritance is not changed.
inheritance is not changed. Examples::

from typing import Protocol

class PColor(Protocol):
def description(self) -> str:
...
def simple_description(self) -> str: # this is a non-protocol member
rerun 'nice color'
@abstractmethod
def add_int(self, x: int) -> int: # protocol member with default
return 42

class SimpleColor(PColor):
def description(self) -> str:
return super().simple_description()
def add_int(self, x: int) -> int:
return super().add_int(x) + x

class BadColor:
def description(self) -> str:
return 'not so nice'

simple: SimpleColor
def represent(c: PColor) -> None:
...
represent(simple) # OK
represent(BadColor()) # Error, 'to_int' is not implemented implicitly

* Second, type checker will statically enforce that a class
actually implements a protocol correctly. In particular, type checker will
always require members without default implementation, to be defined in
a subclass that explicitly subclasses the protocol.
always require members without default implementation to be defined in
a subclass that explicitly subclasses the protocol::

The general philosophy is that protocols are mostly like regular ABCs,
but a static type checker will handle them specially.
In particular, a class can explicitly inherit from multiple protocols.
In this case every method is resolved using normal MRO procedure and type
checker verifies that all subtyping is correct. Note that a concrete method
with appropriate type signature can override abstract but not vice versa.
Examples::
from typing import Protocol, Tuple

from typing import Protocol, Tuple
class RGB(Protocol):
rgb: Tuple[int, int, int]
def intensity(self) -> int:
...
@abstractmethod
def to_bytes(self) -> bytes:
return b''

class PColor(Protocol):
rgb: Tuple[int, int, int]
def description(self) -> str:
...
def simple_description(self) -> str: # This is non-protocol member
rerun 'nice color'
@abstractmethod
def to_bytes(self) -> bytes:
return b''
class Point(RGB):
def __init__(self, red: int, green: int, blue: str) -> None:
self.rgb = red, green, blue # Error, 'blue' must be 'int'
# Error, 'intensity' is not defined!

class SimpleColor(PColor):
def __init__(se;f, red: int, green: int, blue: str) -> None:
self.rgb = red, green, blue # Error, last element is 'str' not 'int'
def description(self) -> str:
return super().simple_description()
# Error, 'to_bytes' is not implemented.
# Default implementation is not implicitly used.
# Note that 'to_bytes' is not required, this is safe
# since the default implementation is actually in the MRO.

The general philosophy is that protocols are mostly like regular ABCs,
but a static type checker will handle them specially.
In particular, a class can explicitly inherit from multiple protocols.
In this case methods are resolved using normal MRO and type checker verifies
that all subtyping are correct. Note that a concrete method with appropriate
type signature can override protocol method but not vice versa. Examples::

class A(Protocol):
def m(self) -> None: ...
def foo(self) -> int: ...
def bar(self) -> str: ...
class B(Protocol):
def m(self) -> None: print('hi')
def m(self) -> None: print('Hi')
def bar(self) -> str: ...

class C(A, B): # Error, abstract 'A.m' can't override non-abstract 'B.m'
class C(A, B): # Fails typecheck, protocol member 'A.m' can't override 'B.m'
...
class C(B, A): # OK
class D(B, A): # OK
def bar(self) -> str:
return 'bar'
# Error, 'foo' is still not implemented.
Expand All @@ -435,9 +460,9 @@ a protocol unless it also has ``Protocol`` as an explicit base class.
Merging and extending protocols
-------------------------------

I think that we should support subprotocols. A subprotocol can be defined
Subprotocols are also supported. A subprotocol can be defined
by having both one or more protocols as the explicit base classes and also
having typing.Protocol as an immediate base class::
having ``typing.Protocol`` as an immediate base class::

from typing import Sized, Protocol

Expand All @@ -446,9 +471,9 @@ having typing.Protocol as an immediate base class::
...

Now the protocol ``SizedAndCloseable`` is a protocol with two methods,
``__len__`` and ``close``. Alternatively, we could have implemented it
like this, assuming the existence of ``SupportsClose``
from an earlier example::
``__len__`` and ``close``. Alternatively, one can implement it
like this, assuming the existence of ``SupportsClose`` from
the previous example::

from typing import Sized

Expand All @@ -457,12 +482,12 @@ from an earlier example::
class SizedAndCloseable(Protocol, Sized, SupportsClose):
pass

The two definitions of ``SizedAndClosable`` would be equivalent.
Subclass relationships between protocols aren't meaningful when
considering subtyping, as we only use structural compatibility
as the criterion, not the MRO.
The two definitions of ``SizedAndClosable`` are equivalent.
Subclass relationships between protocols are not meaningful when
considering subtyping, since the structural compatibility is
the criterion, not the MRO.

If we omit ``Protocol`` in the base class list, this would be regular
If one omits ``Protocol`` in the base class list, this would be regular
(non-protocol) class that must implement ``Sized``. If ``Protocol``
is included in the base class list, all the other base classes
must be protocols. A protocol can't extend a regular class.
Expand Down Expand Up @@ -599,11 +624,24 @@ the behavior of ``Type[]`` is not changed.
------------------------------

Protocols are essentially anonymous. To emphasize this point, static type
checkers might refuse the protocol classes inside ``NewType(...)`` to avoid an
illusion that a distinct type is provided.
checkers might refuse the protocol classes inside ``NewType()`` to avoid an
illusion that a distinct type is provided::

form typing import NewType , Protocol, Iterator

class Id(Protocol):
code: int
secrets: Iterator[bytes]

UserId = NewType('UserId', Id) # Error, can't provide distinct type

On the contrary, type aliases are fully supported, including generic type
aliases.
aliases::

from typing import TypeVar, Reversible, Iterable, Sized

T = TypeVar('T')
CompatReversible = Union[Reversible[T], All[Iterable[T], Sized]]


``@runtime`` decorator and narrowing types by ``isinstance()``
Expand Down Expand Up @@ -798,8 +836,8 @@ a bad idea and classes should need to be explicitly marked as protocols,
as shown in this proposal:

* Protocols don't have some properties of regular classes. In particular,
``isinstance`` is not well-defined for protocols, whereas it's well-defined
(and pretty commonly used) for regular classes.
``isinstance`` is not always well-defined for protocols, whereas it's
well-defined (and pretty commonly used) for regular classes.
* Protocol classes should generally not have (many) method implementations,
as they describe an interface, not an implementation.
Most classes have many implementations, making them bad protocol classes.
Expand Down