如何正确地向Mixin类添加类型提示?

exh*_*uma 8 python type-hinting

请考虑以下示例.这个例子是人为的,但是在一个可运行的例子中说明了这一点:

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))
Run Code Online (Sandbox Code Playgroud)

执行时,这将给出以下输出:

12
20
Run Code Online (Sandbox Code Playgroud)

代码有效.

但运行mypy它会产生以下错误:

example.py:4: error: "MultiplicatorMixin" has no attribute "value"
example.py:10: error: "AdditionMixin" has no attribute "value"
Run Code Online (Sandbox Code Playgroud)

我理解为什么mypy给出了这个结果.但mixin类本身从不使用.它们总是用作额外的超类.

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

但我仍然想知道这样的事情是如何被恰当地暗示的.

Nun*_*dré 24

除了 Campi 关于mypy 建议输入 mixins 的Protocol回答之外:

键入方法的另一种方法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
Run Code Online (Sandbox Code Playgroud)

此外,如果您是TYPE_CHECKING,Protocol并且鉴于您无法转发引用父类(即将父类作为字符串文字传递),解决方法是:

from typing import Protocol, TYPE_CHECKING


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


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

...
Run Code Online (Sandbox Code Playgroud)

  • 这是一个比输入单独的“self”参数更好的答案,因为输入“self”作为协议会阻止您访问 Mixin 类本身的属性。(这只是协议) (5认同)
  • 这感觉比我目前接受的答案要好得多,也更符合当前的状态。感谢更新。注意:我认为你的意思是“Campi”而不是“Dec”;) (2认同)
  • @TuukkaMustonen 不。一个类需要显式继承`Protocol`才能成为一个协议(`class MultiplicationMixin(HasValueProtocol, Protocol): ...`)。否则,它只是继承协议的默认实现。而且,无论如何,协议_可以_包含实际代码。 (2认同)
  • 如果您的 mixin 定义了多个需要相互访问的方法,Campi 的答案将会失败(因为您使用字段注释“self”,而不是 mixin 本身)。这个答案是可行的,您还可以从任何其他所需的类型继承,而协议只能从协议继承。 (2认同)

Cam*_*mpi 21

作为参考,mypy 建议通过协议(https://mypy.readthedocs.io/en/latest/more_types.html#advanced-uses-of-self-types)实现 mixin 。

它适用于 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
Run Code Online (Sandbox Code Playgroud)

所述Protocol基类是在所提供的typing_extensions包的Python 2.7和3.4-3.7。


Gee*_*ado 8

尝试使用:

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

Run Code Online (Sandbox Code Playgroud)

在 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'>)
Run Code Online (Sandbox Code Playgroud)

  • 这似乎过于复杂*只是*为了获得类型提示。 (5认同)

Sra*_*raw 5

我已经在我的机器上测试过了,希望它也适用于你:

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))
Run Code Online (Sandbox Code Playgroud)

  • 当`MyClass` 有很多属性时,我最终会复制所有这些属性,尤其是如果`MyClass` 来自外部依赖项的话。我不认为这是一个可行的解决方案。 (13认同)

soe*_*ace 5

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
Run Code Online (Sandbox Code Playgroud)

Downside is that you have to add the hint to every method, which is kind of cumbersome.

  • 这样会不会破坏mixin的目的?因为现在您只能将其与MyBaseClass的子类一起使用。这意味着您也可以将add方法移至MyBaseClass中。 (3认同)
  • 就我而言,我正在为 django-rest-framework 构建 mixins,它还附带了几个 mixins:https://github.com/encode/django-rest-framework/blob/master/rest_framework/mixins。 py mixin 总是与 [GenericAPIView](https://github.com/encode/django-rest-framework/blob/master/rest_framework/generics.py#L26) 一起使用,提供基本功能,而每个 mixin 都提供不同的附加功能 (3认同)
  • 简化:`MyBaseClass`可以提供各种计算,但用户应该选择支持哪些计算。`MyBaseClass` 提供了一个 `value` 属性,用户可以选择应用程序中需要的 mixins `AdditionMixin`、`SubtractionMixin`、`DivisionMixin` 和 `MultiplicationMixin` 中的哪一个 (3认同)