Advanced uses of self-types

Normally, mypy doesn’t require annotations for the first arguments of instance andclass methods. However, they may be needed to have more precise static typingfor certain programming patterns.

Restricted methods in generic classes

In generic classes some methods may be allowed to be called onlyfor certain values of type arguments:

  1. T = TypeVar('T')
  2.  
  3. class Tag(Generic[T]):
  4. item: T
  5. def uppercase_item(self: Tag[str]) -> str:
  6. return self.item.upper()
  7.  
  8. def label(ti: Tag[int], ts: Tag[str]) -> None:
  9. ti.uppercase_item() # E: Invalid self argument "Tag[int]" to attribute function
  10. # "uppercase_item" with type "Callable[[Tag[str]], str]"
  11. ts.uppercase_item() # This is OK

This pattern also allows matching on nested types in situations where the typeargument is itself generic:

  1. T = TypeVar('T')
  2. S = TypeVar('S')
  3.  
  4. class Storage(Generic[T]):
  5. def __init__(self, content: T) -> None:
  6. self.content = content
  7. def first_chunk(self: Storage[Sequence[S]]) -> S:
  8. return self.content[0]
  9.  
  10. page: Storage[List[str]]
  11. page.first_chunk() # OK, type is "str"
  12.  
  13. Storage(0).first_chunk() # Error: Invalid self argument "Storage[int]" to attribute function
  14. # "first_chunk" with type "Callable[[Storage[Sequence[S]]], S]"

Finally, one can use overloads on self-type to express precise types ofsome tricky methods:

  1. T = TypeVar('T')
  2.  
  3. class Tag(Generic[T]):
  4. @overload
  5. def export(self: Tag[str]) -> str: ...
  6. @overload
  7. def export(self, converter: Callable[[T], str]) -> str: ...
  8.  
  9. def export(self, converter=None):
  10. if isinstance(self.item, str):
  11. return self.item
  12. return converter(self.item)

In particular, an init() method overloaded on self-typemay be useful to annotate generic class constructors where type argumentsdepend on constructor parameters in a non-trivial way, see e.g. Popen.

Mixin classes

Using host class protocol as a self-type in mixin methods allowsmore code re-usability for static typing of mixin classes. For example,one can define a protocol that defines common functionality forhost classes instead of adding required abstract methods to every mixin:

  1. class Lockable(Protocol):
  2. @property
  3. def lock(self) -> Lock: ...
  4.  
  5. class AtomicCloseMixin:
  6. def atomic_close(self: Lockable) -> int:
  7. with self.lock:
  8. # perform actions
  9.  
  10. class AtomicOpenMixin:
  11. def atomic_open(self: Lockable) -> int:
  12. with self.lock:
  13. # perform actions
  14.  
  15. class File(AtomicCloseMixin, AtomicOpenMixin):
  16. def __init__(self) -> None:
  17. self.lock = Lock()
  18.  
  19. class Bad(AtomicCloseMixin):
  20. pass
  21.  
  22. f = File()
  23. b: Bad
  24. f.atomic_close() # OK
  25. b.atomic_close() # Error: Invalid self type for "atomic_close"

Note that the explicit self-type is required to be a protocol whenever itis not a supertype of the current class. In this case mypy will check the validityof the self-type only at the call site.

Precise typing of alternative constructors

Some classes may define alternative constructors. If theseclasses are generic, self-type allows giving them precise signatures:

  1. T = TypeVar('T')
  2.  
  3. class Base(Generic[T]):
  4. Q = TypeVar('Q', bound='Base[T]')
  5.  
  6. def __init__(self, item: T) -> None:
  7. self.item = item
  8.  
  9. @classmethod
  10. def make_pair(cls: Type[Q], item: T) -> Tuple[Q, Q]:
  11. return cls(item), cls(item)
  12.  
  13. class Sub(Base[T]):
  14. ...
  15.  
  16. pair = Sub.make_pair('yes') # Type is "Tuple[Sub[str], Sub[str]]"
  17. bad = Sub[int].make_pair('no') # Error: Argument 1 to "make_pair" of "Base"
  18. # has incompatible type "str"; expected "int"