Skip to content

Commit

Permalink
Minor tweaks to PEP 544 (#1046)
Browse files Browse the repository at this point in the history
This PR contains mostly minor wording tweaks plus a paragraph explicitly allowing class objects as implementations of protocols, previously there were questions whether it is actually allowed, see python/mypy#4536.
  • Loading branch information
ilevkivskyi committed May 13, 2019
1 parent 1df4f57 commit 8aecf9d
Showing 1 changed file with 28 additions and 10 deletions.
38 changes: 28 additions & 10 deletions pep-0544.txt
Original file line number Diff line number Diff line change
Expand Up @@ -703,8 +703,8 @@ intersection type construct could be added in future as specified by PEP 483,
see `rejected`_ ideas for more details.


``Type[]`` with protocols
-------------------------
``Type[]`` and class objects vs protocols
-----------------------------------------

Variables and parameters annotated with ``Type[Proto]`` accept only concrete
(non-protocol) subtypes of ``Proto``. The main reason for this is to allow
Expand Down Expand Up @@ -735,6 +735,23 @@ not explicitly typed, and such assignment creates a type alias.
For normal (non-abstract) classes, the behavior of ``Type[]`` is
not changed.

A class object is considered an implementation of a protocol if accessing
all members on it results in types compatible with the protocol members.
For example::

from typing import Any, Protocol

class ProtoA(Protocol):
def meth(self, x: int) -> int: ...
class ProtoB(Protocol):
def meth(self, obj: Any, x: int) -> int: ...

class C:
def meth(self, x: int) -> int: ...

a: ProtoA = C # Type check error, signatures don't match!
b: ProtoB = C # OK


``NewType()`` and type aliases
------------------------------
Expand Down Expand Up @@ -762,8 +779,8 @@ aliases::
CompatReversible = Union[Reversible[T], SizedIterable[T]]


Modules as subtypes of protocols
--------------------------------
Modules as implementations of protocols
---------------------------------------

A module object is accepted where a protocol is expected if the public
interface of the given module is compatible with the expected protocol.
Expand Down Expand Up @@ -1177,7 +1194,8 @@ For example::

def do_stuff(o: Union[P, X]) -> int:
if isinstance(o, P):
return o.common_method_name(1) # oops, what if it's an X instance?
return o.common_method_name(1) # Results in TypeError not caught
# statically if o is an X instance.

Another potentially problematic case is assignment of attributes
*after* instantiation::
Expand All @@ -1196,13 +1214,13 @@ Another potentially problematic case is assignment of attributes

def f(x: Union[P, int]) -> None:
if isinstance(x, P):
# static type of x is P here
# Static type of x is P here.
...
else:
# type of x is "int" here?
# Static type of x is int, but can be other type at runtime...
print(x + 1)

f(C()) # oops
f(C()) # ...causing a TypeError.

We argue that requiring an explicit class decorator would be better, since
one can then attach warnings about problems like this in the documentation.
Expand Down Expand Up @@ -1273,7 +1291,7 @@ Consider this example::

c = C()
f(c) # Would typecheck if covariant subtyping
# of mutable attributes were allowed
# of mutable attributes were allowed.
c.x >> 1 # But this fails at runtime

It was initially proposed to allow this for practical reasons, but it was
Expand All @@ -1292,7 +1310,7 @@ However, it was decided not to do this because of several downsides:

T = TypeVar('T')

class P(Protocol[T]): # Declared as invariant
class P(Protocol[T]): # Protocol is declared as invariant.
def meth(self) -> T:
...
class C:
Expand Down

0 comments on commit 8aecf9d

Please sign in to comment.