@@ -703,8 +703,8 @@ intersection type construct could be added in future as specified by PEP 483,
703
703
see `rejected`_ ideas for more details.
704
704
705
705
706
- ``Type[]`` with protocols
707
- -------------------------
706
+ ``Type[]`` and class objects vs protocols
707
+ -----------------------------------------
708
708
709
709
Variables and parameters annotated with ``Type[Proto]`` accept only concrete
710
710
(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.
735
735
For normal (non-abstract) classes, the behavior of ``Type[]`` is
736
736
not changed.
737
737
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
+
738
755
739
756
``NewType()`` and type aliases
740
757
------------------------------
@@ -762,8 +779,8 @@ aliases::
762
779
CompatReversible = Union[Reversible[T], SizedIterable[T]]
763
780
764
781
765
- Modules as subtypes of protocols
766
- --------------------------------
782
+ Modules as implementations of protocols
783
+ ---------------------------------------
767
784
768
785
A module object is accepted where a protocol is expected if the public
769
786
interface of the given module is compatible with the expected protocol.
@@ -1177,7 +1194,8 @@ For example::
1177
1194
1178
1195
def do_stuff(o: Union[P, X]) -> int:
1179
1196
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.
1181
1199
1182
1200
Another potentially problematic case is assignment of attributes
1183
1201
*after* instantiation::
@@ -1196,13 +1214,13 @@ Another potentially problematic case is assignment of attributes
1196
1214
1197
1215
def f(x: Union[P, int]) -> None:
1198
1216
if isinstance(x, P):
1199
- # static type of x is P here
1217
+ # Static type of x is P here.
1200
1218
...
1201
1219
else:
1202
- # type of x is " int" here?
1220
+ # Static type of x is int, but can be other type at runtime...
1203
1221
print(x + 1)
1204
1222
1205
- f(C()) # oops
1223
+ f(C()) # ...causing a TypeError.
1206
1224
1207
1225
We argue that requiring an explicit class decorator would be better, since
1208
1226
one can then attach warnings about problems like this in the documentation.
@@ -1273,7 +1291,7 @@ Consider this example::
1273
1291
1274
1292
c = C()
1275
1293
f(c) # Would typecheck if covariant subtyping
1276
- # of mutable attributes were allowed
1294
+ # of mutable attributes were allowed.
1277
1295
c.x >> 1 # But this fails at runtime
1278
1296
1279
1297
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:
1292
1310
1293
1311
T = TypeVar('T')
1294
1312
1295
- class P(Protocol[T]): # Declared as invariant
1313
+ class P(Protocol[T]): # Protocol is declared as invariant.
1296
1314
def meth(self) -> T:
1297
1315
...
1298
1316
class C:
0 commit comments