29

考虑以下示例。该示例是人为设计的,但在一个可运行的示例中说明了这一点:

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 类从不单独使用。它们总是用作附加的超类。

对于上下文,这是一种已在现有应用程序中使用的模式,我正在添加类型提示。在这种情况下,错误是误报。我正在考虑使用 mixins 重写该部分,因为我不是特别喜欢它,并且可能通过重新组织类层次结构来完成相同的操作。

但我仍然想知道如何正确暗示这样的事情。

4

8 回答 8

29

作为参考,mypy 建议通过Protocol(documentation here ) 来实现 mixins。

它适用于 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

Python 2.7 和 3.4-3.7的包中Protocol提供了基类。typing_extensions

于 2019-12-01T19:09:01.570 回答
9

我在这个问题中看到的一种方法是类型提示self属性。与Union打字包一起,您可以使用与您的mixin一起使用的类中的属性,同时仍然对自己的属性有正确的类型提示:

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

缺点是您必须在每个方法中添加提示,这有点麻烦。

于 2018-09-13T11:47:02.527 回答
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))
于 2018-08-20T12:55:46.610 回答
4

尝试:

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

在 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'>)
于 2020-07-09T21:04:14.150 回答
1

除了上面提到的好答案。我的用例 - 用于测试的 mixins。

正如 Guido van Rossum 本人在这里提出的:

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    

因此,当涉及到 mixin 时,它可能如下所示:


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)
于 2019-09-19T08:53:22.050 回答
1

我的解决方案:value: int在 Mixin 类中添加一个没有任何初始化的:

class MultiplicatorMixin:
    value: int

    def multiply(self, m: int) -> int:
        return self.value * m


class AdditionMixin:
    value: 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))
于 2022-01-14T13:36:04.450 回答
1

除了 Campi 关于mypy 建议使用以下方式键入 mixinsProtocol的回答之外:

键入方法的替代方法self只是继承协议。

from typing import Protocol


class HasValueProtocol(Protocol):
    @property
    def value(self) -> int: ...


class MultiplicationMixin(HasValueProtocol):

    def multiply(self, m: int) -> int:
        return self.value * m


class AdditionMixin(HasValueProtocol):

    def add(self, b: int) -> int:
        return self.value + b


class MyClass(MultiplicationMixin, AdditionMixin):

    def __init__(self, value: int) -> None:
        self.value = value
于 2022-01-29T16:50:18.733 回答
0

您不必在每个方法中都编写类型提示的一种方法:

import typing


class FooMixin:
    base = typing.Union["Hello", "World"]

    def alpha(self: base):
        self.hello()

    def beta(self: base):
        self.world()


class Base(object):
    pass


class Hello(Base, FooMixin):
    def hello(self):
        print("hello from", self)


class World(Base, FooMixin):
    def world(self):
        print("world from", self)


Hello().alpha()
World().beta()
于 2022-01-14T05:24:03.157 回答