Consider the following example. The example is contrived but illustrates the point in a runnable example:
class MultiplicatorMixin:
def multiply(self, m: int) -> int:
return self.value * m
class AdditionMixin:
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))
When executed this will give the following output:
12
20
The code works.
But running mypy
on it, yields the following errors:
example.py:4: error: "MultiplicatorMixin" has no attribute "value"
example.py:10: error: "AdditionMixin" has no attribute "value"
I understand why mypy gives this result. But the mixin classes are never used by themselves. They are always used as additional superclasses.
For context, this is a pattern which has been used in an existing application and I am in the process of adding type-hints. And in this case, the errors are false-positives. I am thinking about rewriting the part using the mixins as I don't particularly like it and the same could probably be done with reorganising the class hierarchy.
But I still would like to know how something like this could be properly hinted.
For reference, mypy recommends to implement mixins through a Protocol
(documentation here).
It works with mypy >= 750.
from typing 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
The Protocol
base class is provided in the typing_extensions
package for Python 2.7 and 3.4-3.7.