如何正确地向 Mixin 类 添加类型提示?
How do I correctly add type-hints to Mixin classes?
考虑以下示例。该示例是人为设计的,但在可运行示例中说明了这一点:
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 classes 从来没有被自己使用过。它们总是用作额外的 superclasses.
就上下文而言,这是一种已在现有应用程序中使用的模式,我正在添加类型提示。在这种情况下,错误是误报。我正在考虑使用 mixin 重写该部分,因为我不是特别喜欢它,并且可以通过重新组织 class 层次结构来完成同样的操作。
但我还是很想知道这样的事情怎么才能正确提示。
我已经在我的机器上测试过了,希望它也适用于你:
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))
我在 中看到的一种方法是类型提示 self
属性。与 typing 包中的 Union
一起,您可以使用与您的 mixin 一起使用的 class 中的属性,同时仍然为自己的属性提供正确的类型提示:
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
缺点是每一个方法都要添加hint,比较麻烦
除了上面提到的好的答案。我的用例 - 用于测试的混合。
Guido van Rossum 本人提议 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
因此,当涉及到 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 如下所示:
class TestSomething(unittest.TestCase, UsefulMixin, AnotherUsefulMixin):
def test_something(self):
self.something_useful(10, 'Alice')
self.something_else_useful(True, False, None)
作为参考,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
Protocol
基础 class 在 Python 2.7 和 3.4-3.7 的 typing_extensions
包中提供。
试试:
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'>)
一个不需要在每个方法都写类型提示的方法:
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()
我的解决方案:在 Mixin class:
中添加一个没有任何初始化的 value: int
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))
除了 Campi 关于 the mypy's recommendation of typing mixins with 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
此外,如果您是 TYPE_CHECKING
一个 Protocol
,并且假设您不能转发引用父 class(即将父 class 作为字符串文字传递),解决方法是:
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
...
考虑以下示例。该示例是人为设计的,但在可运行示例中说明了这一点:
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 classes 从来没有被自己使用过。它们总是用作额外的 superclasses.
就上下文而言,这是一种已在现有应用程序中使用的模式,我正在添加类型提示。在这种情况下,错误是误报。我正在考虑使用 mixin 重写该部分,因为我不是特别喜欢它,并且可以通过重新组织 class 层次结构来完成同样的操作。
但我还是很想知道这样的事情怎么才能正确提示。
我已经在我的机器上测试过了,希望它也适用于你:
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))
我在 self
属性。与 typing 包中的 Union
一起,您可以使用与您的 mixin 一起使用的 class 中的属性,同时仍然为自己的属性提供正确的类型提示:
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
缺点是每一个方法都要添加hint,比较麻烦
除了上面提到的好的答案。我的用例 - 用于测试的混合。
Guido van Rossum 本人提议 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
因此,当涉及到 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 如下所示:
class TestSomething(unittest.TestCase, UsefulMixin, AnotherUsefulMixin):
def test_something(self):
self.something_useful(10, 'Alice')
self.something_else_useful(True, False, None)
作为参考,mypy 推荐通过 Protocol
(documentation here).
适用于 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
Protocol
基础 class 在 Python 2.7 和 3.4-3.7 的 typing_extensions
包中提供。
试试:
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'>)
一个不需要在每个方法都写类型提示的方法:
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()
我的解决方案:在 Mixin class:
中添加一个没有任何初始化的value: int
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))
除了 Campi 关于 the mypy's recommendation of typing mixins with 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
此外,如果您是 TYPE_CHECKING
一个 Protocol
,并且假设您不能转发引用父 class(即将父 class 作为字符串文字传递),解决方法是:
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
...