Use a TypeVar to parameterise over the type of self. This is always an instance of "the current class", allowing to use the TypeVar to express the type respecting inheritance:
from typing import TypeVar
Self = TypeVar('Self')
class A:
# `bar` must be of the same type as `self`
def foo(self: Self, bar: Self):
...
class B(A):
...
When calling A().foo or B().foo, Self is automatically inferred to the concrete class A or B, respectively. This then automatically constraints the other parameter to match the inferred TypeVar and thus the class.
The same mechanism can be used for @classmethods as well. Instead of self: Self to capture the type of the instance, use cls: Type[Self] to capture the type directly.
from typing import TypeVar, Type
Self = TypeVar('Self')
class A:
# `bar` must be an instance of `cls`
@classmethod
def foo(cls: Type[Self], bar: Self):
...
class B(A):
...