Рассмотрим следующий пример. Пример придуман, но иллюстрирует точку в работающем примере:

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))

При выполнении это даст следующий вывод:

12
20

Код работает.

Но выполнение mypy на нем приводит к следующим ошибкам:

example.py:4: error: "MultiplicatorMixin" has no attribute "value"
example.py:10: error: "AdditionMixin" has no attribute "value"

Я понимаю, почему mypy дает такой результат. Но классы mixin никогда не используются сами по себе. Они всегда используются в качестве дополнительных суперклассов.

Для контекста это шаблон, который использовался в существующем приложении, и я нахожусь в процессе добавления подсказок типа. И в этом случае ошибки являются ложноположительными. Я думаю о переписывании части, используя миксины, поскольку мне это не особенно нравится, и то же самое, вероятно, можно было бы сделать с реорганизацией иерархии классов.

Но я все еще хотел бы знать, как что-то подобное можно правильно намекнуть.

10
exhuma 20 Авг 2018 в 15:02

4 ответа

Лучший ответ

Я проверил это на своей машине, надеюсь, это также будет работать для вас:

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))
4
Sraw 20 Авг 2018 в 12:55

Для справки mypy рекомендует реализовывать миксины через протокол (

Campi 1 Дек 2019 в 19:09

В дополнение к хорошим ответам, упомянутым выше. Мой вариант использования - миксин для использования в тестах.

По предложению самого Гвидо ван Россума здесь:

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    

Таким образом, когда дело доходит до миксина, это может выглядеть следующим образом:


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)  

И наш последний класс будет выглядеть следующим образом:

class TestSomething(unittest.TestCase, UsefulMixin, AnotherUsefulMixin):

    def test_something(self):
        self.something_useful(10, 'Alice')
        self.something_else_useful(True, False, None)
0
Artur Barseghyan 19 Сен 2019 в 08:53

Один из подходов, которые я видел в этом вопросе, - это подсказка типа для атрибута self. Вместе с Union из пакета для ввода вы можете использовать атрибуты из класса, который используется вместе с вашим миксином, при этом сохраняя правильную подсказку типа для собственных атрибутов:

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

Недостатком является то, что вы должны добавить подсказку к каждому методу, что довольно громоздко.

6
soerface 13 Сен 2018 в 12:23
51930339