Generic protocols

Mypy supports generic protocols (see also Protocols and structural subtyping). Severalpredefined protocols are generic, such asIterable[T], and you can define additional generic protocols. Genericprotocols mostly follow the normal rules for generic classes. Example:

  1. from typing import TypeVar
  2. from typing_extensions import Protocol
  3.  
  4. T = TypeVar('T')
  5.  
  6. class Box(Protocol[T]):
  7. content: T
  8.  
  9. def do_stuff(one: Box[str], other: Box[bytes]) -> None:
  10. ...
  11.  
  12. class StringWrapper:
  13. def __init__(self, content: str) -> None:
  14. self.content = content
  15.  
  16. class BytesWrapper:
  17. def __init__(self, content: bytes) -> None:
  18. self.content = content
  19.  
  20. do_stuff(StringWrapper('one'), BytesWrapper(b'other')) # OK
  21.  
  22. x: Box[float] = ...
  23. y: Box[int] = ...
  24. x = y # Error -- Box is invariant

The main difference between generic protocols and ordinary genericclasses is that mypy checks that the declared variances of generictype variables in a protocol match how they are used in the protocoldefinition. The protocol in this example is rejected, since the typevariable T is used covariantly as a return type, but the typevariable is invariant:

  1. from typing import TypeVar
  2. from typing_extensions import Protocol
  3.  
  4. T = TypeVar('T')
  5.  
  6. class ReadOnlyBox(Protocol[T]): # Error: covariant type variable expected
  7. def content(self) -> T: ...

This example correctly uses a covariant type variable:

  1. from typing import TypeVar
  2. from typing_extensions import Protocol
  3.  
  4. T_co = TypeVar('T_co', covariant=True)
  5.  
  6. class ReadOnlyBox(Protocol[T_co]): # OK
  7. def content(self) -> T_co: ...
  8.  
  9. ax: ReadOnlyBox[float] = ...
  10. ay: ReadOnlyBox[int] = ...
  11. ax = ay # OK -- ReadOnlyBox is covariant

See Variance of generic types for more about variance.

Generic protocols can also be recursive. Example:

  1. T = TypeVar('T')
  2.  
  3. class Linked(Protocol[T]):
  4. val: T
  5. def next(self) -> 'Linked[T]': ...
  6.  
  7. class L:
  8. val: int
  9.  
  10. ... # details omitted
  11.  
  12. def next(self) -> 'L':
  13. ... # details omitted
  14.  
  15. def last(seq: Linked[T]) -> T:
  16. ... # implementation omitted
  17.  
  18. result = last(L()) # Inferred type of 'result' is 'int'