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.
This commit is contained in:
Ivan Levkivskyi 2019-05-13 11:22:49 +01:00 committed by GitHub
parent 1df4f57758
commit 8aecf9d07a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 28 additions and 10 deletions

View File

@ -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: