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:
- T = TypeVar('T')
- class Tag(Generic[T]):
- item: T
- def uppercase_item(self: Tag[str]) -> str:
- return self.item.upper()
- def label(ti: Tag[int], ts: Tag[str]) -> None:
- ti.uppercase_item() # E: Invalid self argument "Tag[int]" to attribute function
- # "uppercase_item" with type "Callable[[Tag[str]], str]"
- ts.uppercase_item() # This is OK
This pattern also allows matching on nested types in situations where the typeargument is itself generic:
- T = TypeVar('T')
- S = TypeVar('S')
- class Storage(Generic[T]):
- def __init__(self, content: T) -> None:
- self.content = content
- def first_chunk(self: Storage[Sequence[S]]) -> S:
- return self.content[0]
- page: Storage[List[str]]
- page.first_chunk() # OK, type is "str"
- Storage(0).first_chunk() # Error: Invalid self argument "Storage[int]" to attribute function
- # "first_chunk" with type "Callable[[Storage[Sequence[S]]], S]"
Finally, one can use overloads on self-type to express precise types ofsome tricky methods:
- T = TypeVar('T')
- class Tag(Generic[T]):
- @overload
- def export(self: Tag[str]) -> str: ...
- @overload
- def export(self, converter: Callable[[T], str]) -> str: ...
- def export(self, converter=None):
- if isinstance(self.item, str):
- return self.item
- 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:
- class Lockable(Protocol):
- @property
- def lock(self) -> Lock: ...
- class AtomicCloseMixin:
- def atomic_close(self: Lockable) -> int:
- with self.lock:
- # perform actions
- class AtomicOpenMixin:
- def atomic_open(self: Lockable) -> int:
- with self.lock:
- # perform actions
- class File(AtomicCloseMixin, AtomicOpenMixin):
- def __init__(self) -> None:
- self.lock = Lock()
- class Bad(AtomicCloseMixin):
- pass
- f = File()
- b: Bad
- f.atomic_close() # OK
- 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:
- T = TypeVar('T')
- class Base(Generic[T]):
- Q = TypeVar('Q', bound='Base[T]')
- def __init__(self, item: T) -> None:
- self.item = item
- @classmethod
- def make_pair(cls: Type[Q], item: T) -> Tuple[Q, Q]:
- return cls(item), cls(item)
- class Sub(Base[T]):
- ...
- pair = Sub.make_pair('yes') # Type is "Tuple[Sub[str], Sub[str]]"
- bad = Sub[int].make_pair('no') # Error: Argument 1 to "make_pair" of "Base"
- # has incompatible type "str"; expected "int"