Skip to content

Commit 8aecf9d

Browse files
authored
Minor tweaks to PEP 544 (#1046)
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.
1 parent 1df4f57 commit 8aecf9d

File tree

1 file changed

+28
-10
lines changed

1 file changed

+28
-10
lines changed

pep-0544.txt

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -703,8 +703,8 @@ intersection type construct could be added in future as specified by PEP 483,
703703
see `rejected`_ ideas for more details.
704704

705705

706-
``Type[]`` with protocols
707-
-------------------------
706+
``Type[]`` and class objects vs protocols
707+
-----------------------------------------
708708

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

738+
A class object is considered an implementation of a protocol if accessing
739+
all members on it results in types compatible with the protocol members.
740+
For example::
741+
742+
from typing import Any, Protocol
743+
744+
class ProtoA(Protocol):
745+
def meth(self, x: int) -> int: ...
746+
class ProtoB(Protocol):
747+
def meth(self, obj: Any, x: int) -> int: ...
748+
749+
class C:
750+
def meth(self, x: int) -> int: ...
751+
752+
a: ProtoA = C # Type check error, signatures don't match!
753+
b: ProtoB = C # OK
754+
738755

739756
``NewType()`` and type aliases
740757
------------------------------
@@ -762,8 +779,8 @@ aliases::
762779
CompatReversible = Union[Reversible[T], SizedIterable[T]]
763780

764781

765-
Modules as subtypes of protocols
766-
--------------------------------
782+
Modules as implementations of protocols
783+
---------------------------------------
767784

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

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

11821200
Another potentially problematic case is assignment of attributes
11831201
*after* instantiation::
@@ -1196,13 +1214,13 @@ Another potentially problematic case is assignment of attributes
11961214

11971215
def f(x: Union[P, int]) -> None:
11981216
if isinstance(x, P):
1199-
# static type of x is P here
1217+
# Static type of x is P here.
12001218
...
12011219
else:
1202-
# type of x is "int" here?
1220+
# Static type of x is int, but can be other type at runtime...
12031221
print(x + 1)
12041222

1205-
f(C()) # oops
1223+
f(C()) # ...causing a TypeError.
12061224

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

12741292
c = C()
12751293
f(c) # Would typecheck if covariant subtyping
1276-
# of mutable attributes were allowed
1294+
# of mutable attributes were allowed.
12771295
c.x >> 1 # But this fails at runtime
12781296

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

12931311
T = TypeVar('T')
12941312

1295-
class P(Protocol[T]): # Declared as invariant
1313+
class P(Protocol[T]): # Protocol is declared as invariant.
12961314
def meth(self) -> T:
12971315
...
12981316
class C:

0 commit comments

Comments
 (0)