From b8a83cfa8ae6e6be64302f4b17a1a91ccdffd5ec Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 12 May 2019 00:57:34 +0100 Subject: [PATCH 1/2] Minor tweaks to PEP 544 --- pep-0544.txt | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/pep-0544.txt b/pep-0544.txt index bd567228ecd..3a51d878967 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,14 @@ 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? + # Statically type of x is int statically, + # 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 +1292,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 +1311,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: From da6a92c1e7c33f0389a793585d9e82256d24f07b Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 12 May 2019 01:53:16 +0100 Subject: [PATCH 2/2] Remove duplicate word (and reflow the comment) --- pep-0544.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pep-0544.txt b/pep-0544.txt index 3a51d878967..9476121731e 100644 --- a/pep-0544.txt +++ b/pep-0544.txt @@ -1217,8 +1217,7 @@ Another potentially problematic case is assignment of attributes # Static type of x is P here. ... else: - # Statically type of x is int statically, - # can be other type at runtime... + # Static type of x is int, but can be other type at runtime... print(x + 1) f(C()) # ...causing a TypeError.