2Tests subtyping rules for protocols.
5# Specification: https://typing.readthedocs.io/en/latest/spec/protocol.html#subtyping-relationships-with-other-types
7# > Protocols cannot be instantiated.
9from typing import Hashable, Iterable, Protocol, Sequence, TypeVar
12class Proto1(Protocol):
16p1 = Proto1() # E: protocol cannot be instantiated
[call-non-callable] Cannot instantiate class `Proto1`: This call will raise `TypeError` at runtime
19# > A protocol is never a subtype of a concrete type.
21# > A concrete type X is a subtype of protocol P if and only if X implements
22# > all protocol members of P with compatible types. In other words, subtyping
23# > with respect to a protocol is always structural.
26class Proto2(Protocol):
27 def method1(self) -> None:
32 def method1(self) -> None:
36def func1(p2: Proto2, c2: Concrete2):
38 v2: Concrete2 = p2 # E
[invalid-assignment] Object of type `Proto2` is not assignable to `Concrete2`
41# > A protocol P1 is a subtype of another protocol P2 if P1 defines all
42# > protocol members of P2 with compatible types.
45class Proto3(Protocol):
46 def method1(self) -> None:
49 def method2(self) -> None:
53def func2(p2: Proto2, p3: Proto3):
[invalid-assignment] Object of type `Proto2` is not assignable to `Proto3`
58# > Generic protocols follow the rules for generic abstract classes, except
59# > for using structural compatibility instead of compatibility defined by
60# > inheritance relationships.
66class Proto4(Protocol[S, T]):
67 def method1(self, a: S, b: T) -> tuple[S, T]:
71class Proto5(Protocol[T]):
72 def method1(self, a: T, b: T) -> tuple[T, T]:
76def func3(p4_int: Proto4[int, int], p5_int: Proto5[int]):
77 v1: Proto4[int, int] = p5_int # OK
78 v2: Proto5[int] = p4_int # OK
79 v3: Proto4[int, float] = p5_int # E
[invalid-assignment] Object of type `Proto5[int]` is not assignable to `Proto4[int, int | float]`
80 v4: Proto5[float] = p4_int # E
[invalid-assignment] Object of type `Proto4[int, int]` is not assignable to `Proto5[int | float]`
83S_co = TypeVar("S_co", covariant=True)
84T_contra = TypeVar("T_contra", contravariant=True)
87class Proto6(Protocol[S_co, T_contra]):
88 def method1(self, a: T_contra) -> Sequence[S_co]:
92class Proto7(Protocol[S_co, T_contra]):
93 def method1(self, a: T_contra) -> Sequence[S_co]:
97def func4(p6: Proto6[float, float]):
98 v1: Proto7[object, int] = p6 # OK
99 v2: Proto7[float, float] = p6 # OK
100 v3: Proto7[complex, int] = p6 # OK
102 v4: Proto7[int, float] = p6 # E
[invalid-assignment] Object of type `Proto6[int | float, int | float]` is not assignable to `Proto7[int, int | float]`
103 v5: Proto7[float, object] = p6 # E
[invalid-assignment] Object of type `Proto6[int | float, int | float]` is not assignable to `Proto7[int | float, object]`
106# > Unions of protocol classes behaves the same way as for non-protocol classes.
109class SupportsExit(Protocol):
110 def exit(self) -> int:
114class SupportsQuit(Protocol):
115 def quit(self) -> int | None:
119def finish(task: SupportsExit | SupportsQuit) -> int:
124 def quit(self) -> int:
128finish(DefaultJob()) # OK
131# > One can use multiple inheritance to define an intersection of protocols.
134class HashableFloats(Iterable[float], Hashable, Protocol):
138def cached_func(args: HashableFloats) -> float:
142cached_func((1, 2, 3)) # OK, tuple is both hashable and iterable