diff --git a/pep-0544.txt b/pep-0544.txt index bd567228ecd..9476121731e 100644 --- a/pep-0544.txt +++ b/pep-0544.txt @@ -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 @@ -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 ------------------------------ @@ -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. @@ -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:: @@ -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. @@ -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 @@ -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: