Consider the following example. The example is contrived but illustrates the point in a runnable example:
class MultiplicatorMixin:
def multiply(self, m: in
For reference, mypy recommends to implement mixins through a protocol (https://mypy.readthedocs.io/en/latest/more_types.html#advanced-uses-of-self-types).
It works with mypy >= 750.
from typing_extensions import Protocol
class HasValueProtocol(Protocol):
@property
def value(self) -> int: ...
class MultiplicationMixin:
def multiply(self: HasValueProtocol, m: int) -> int:
return self.value * m
class AdditionMixin:
def add(self: HasValueProtocol, b: int) -> int:
return self.value + b
class MyClass(MultiplicationMixin, AdditionMixin):
def __init__(self, value: int) -> None:
self.value = value
One approach I saw in this question is type hinting the self
attribute. Together with Union
from the typing package, you are able to use the attributes from a class which is used together with your mixin, while still having correct type hinting for own attributes:
from typing import Union
class AdditionMixin:
def add(self: Union[MyBaseClass, 'AdditionMixin'], b: int) -> int:
return self.value + b
class MyBaseClass:
def __init__(self, value: int):
self.value = value
Downside is that you have to add the hint to every method, which is kind of cumbersome.
I've tested it on my machine, hope it will also work for you:
class MultiplicatorMixin:
value = None # type: int
def multiply(self, m: int) -> int:
return self.value * m
class AdditionMixin:
value = None # type: int
def add(self, b: int) -> int:
return self.value + b
class MyClass(MultiplicatorMixin, AdditionMixin):
def __init__(self, value: int) -> None:
self.value = value
instance = MyClass(10)
print(instance.add(2))
print(instance.multiply(2))
Try with:
from typing import Type, TYPE_CHECKING, TypeVar
T = TypeVar('T')
def with_typehint(baseclass: Type[T]) -> Type[T]:
"""
Useful function to make mixins with baseclass typehint
```
class ReadonlyMixin(with_typehint(BaseAdmin))):
...
```
"""
if TYPE_CHECKING:
return baseclass
return object
Example tested in Pyright:
class ReadOnlyInlineMixin(with_typehint(BaseModelAdmin)):
def get_readonly_fields(self,
request: WSGIRequest,
obj: Optional[Model] = None) -> List[str]:
if self.readonly_fields is None:
readonly_fields = []
else:
readonly_fields = self.readonly_fields # self get is typed by baseclass
return self._get_readonly_fields(request, obj) + list(readonly_fields)
def has_change_permission(self,
request: WSGIRequest,
obj: Optional[Model] = None) -> bool:
return (
request.method in ['GET', 'HEAD']
and super().has_change_permission(request, obj) # super is typed by baseclass
)
>>> ReadOnlyAdminMixin.__mro__
(<class 'custom.django.admin.mixins.ReadOnlyAdminMixin'>, <class 'object'>)
In addition to good answers mentioned above. My use case - mixins to be used in tests.
As proposed by Guido van Rossum himself here:
from typing import *
T = TypeVar('T')
class Base:
fit: Callable
class Foo(Base):
def fit(self, arg1: int) -> Optional[str]:
pass
class Bar(Foo):
def fit(self, arg1: float) -> str:
pass
Thus, when it comes to a mixin, it could look as follows:
class UsefulMixin:
assertLess: Callable
assertIn: Callable
assertIsNotNone: Callable
def something_useful(self, key, value):
self.assertIsNotNone(key)
self.assertLess(key, 10)
self.assertIn(value, ['Alice', 'in', 'Wonderland']
class AnotherUsefulMixin:
assertTrue: Callable
assertFalse: Callable
assertIsNone: Callable
def something_else_useful(self, val, foo, bar):
self.assertTrue(val)
self.assertFalse(foo)
self.assertIsNone(bar)
And our final class would look as follows:
class TestSomething(unittest.TestCase, UsefulMixin, AnotherUsefulMixin):
def test_something(self):
self.something_useful(10, 'Alice')
self.something_else_useful(True, False, None)