python - 静态类型化枚举的抽象方法
问题描述
从抽象超类枚举Action
和Activity
继承的枚举:
ActivityA
可以在其ActionA
上执行;和ActivityB
可以在其ActionB
上执行。
如何将类型声明添加到抽象方法action
的方法参数中,以便 MyPy 尊重继承或哪个操作适用于哪个活动?perform
Activity
from abc import ABC, ABCMeta, abstractmethod
from enum import EnumMeta, IntEnum
class ABCEnumMeta(EnumMeta, ABCMeta):
...
class Action(ABC, IntEnum, metaclass=ABCEnumMeta):
...
class ActionA(Action):
start = 1
stop = 2
class ActionB(Action):
start = 1
pause = 2
resume = 3
complete = 4
fail = 5
class Activity(ABC, IntEnum, metaclass=ABCEnumMeta):
@abstractmethod
def perform(
self: "Activity",
action, # <- This line
) -> str:
...
class ActivityA(Activity):
this = 1
that = 2
def perform(
self: "ActivityA",
action: ActionA,
) -> str:
return f"A: {action.name} {self.name}"
class ActivityB(Activity):
something = 1
another = 2
def perform(
self: "ActivityB",
action: ActionB,
) -> str:
return f"B: {action.name} {self.name}"
print( ActivityB.something.perform(ActionB.pause) )
print( ActivityA.this.perform(ActionA.stop) )
print( ActivityB.another.perform(ActionA.start) )
print( ActivityA.that.perform(ActionB.fail) )
正在使用的mypy.ini
设置文件是:
[mypy]
disallow_any_expr = True
disallow_any_decorated = True
disallow_any_explicit = True
disallow_any_generics = True
disallow_subclassing_any = True
disallow_untyped_calls = True
disallow_untyped_defs = True
disallow_incomplete_defs = True
MyPy 的输出是:
test_enum.py:26: error: Function is missing a type annotation for one or more arguments test_enum.py:26: error: Type of decorated function contains type "Any" ("Callable[[Activity, Any], str]") test_enum.py:56: error: Argument 1 to "perform" of "ActivityB" has incompatible type "ActionA"; expected "ActionB" test_enum.py:57: error: Argument 1 to "perform" of "ActivityA" has incompatible type "ActionB"; expected "ActionA"
(最后两个错误是预期的。)
在这种情况下,我通常会使用泛型并定义:
A = TypeVar("A", bound=Action)
class Activity(ABC, Generic[A], IntEnum, metaclass=ABCEnumMeta):
@abstractmethod
def perform(
self: "Activity",
action A,
) -> str:
...
class ActivityA(Activity[ActionA]):
...
class ActivityB(Activity[ActionB]):
...
但是,Enum
当您尝试添加泛型时,该类会引发异常。
如何解决这个问题以正确定义抽象方法的参数类型?
解决方案
部分解决方案(因为Enum
不能有Generic
mixin)是在抽象父方法中使用超类型Action
(而不是尝试通过泛型使用子类型):
class Activity(ABC, IntEnum, metaclass=ABCEnumMeta):
@abstractmethod
def perform(
self: "Activity",
action: Action,
) -> str:
...
这将给出错误:
test_enum.py:36: error: Argument 1 of "perform" is incompatible with supertype "Activity"; supertype defines the argument type as "Action" test_enum.py:36: note: This violates the Liskov substitution principle test_enum.py:36: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides test_enum.py:47: error: Argument 1 of "perform" is incompatible with supertype "Activity"; supertype defines the argument type as "Action" test_enum.py:47: note: This violates the Liskov substitution principle test_enum.py:47: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides test_enum.py:56: error: Argument 1 to "perform" of "ActivityB" has incompatible type "ActionA"; expected "ActionB" test_enum.py:57: error: Argument 1 to "perform" of "ActivityA" has incompatible type "ActionB"; expected "ActionA"
可以使用以下方法在本地# type: ignore[override]
显式地消除其他错误:
class ActivityA(Activity):
this = 1
that = 2
def perform( # type: ignore[override]
self: "ActivityA",
action: ActionA,
) -> str:
return f"A: {action.name} {self.name}"
class ActivityB(Activity):
something = 1
another = 2
def perform( # type: ignore[override]
self: "ActivityB",
action: ActionB,
) -> str:
return f"B: {action.name} {self.name}"
一旦这些错误在本地被静音,那么输出就是两个预期的错误:
test_enum.py:56: error: Argument 1 to "perform" of "ActivityB" has incompatible type "ActionA"; expected "ActionB" test_enum.py:57: error: Argument 1 to "perform" of "ActivityA" has incompatible type "ActionB"; expected "ActionA"
虽然这清楚地表明抽象父方法应该Action
在参数中采用一个类型,但它并没有从抽象父类中表明子类应该期待一个特定的子类型(因为泛型允许)。因此,这只是部分解决方案;更好的解决方案是“修复”Enum
类(或子类Enum
)以允许Generic
混合。
推荐阅读
- java - Raspberry Pi 上的 Java Audio Clip#getFramePosition() 非常慢
- typescript - 带有 TypeScript 的 create-react-app 中的样式化组件 babel 插件?
- tinymce - 加载插件失败:来自 url 的 advlink
- laravel-5 - laravel 查询生成器 whereIn 获取重复数据
- drupal - Drupal 8 - 如何在提交网络表单后注销用户
- java - POST 响应 HTML 页面显示在调试器中,但不在使用 JAVAEE 的浏览器中
- intellij-idea - 在 pubspec.yaml 中导入 geoloaction 包时出错
- android - 播放 mediaFile 时权限被拒绝
- python - 在 python 3.7 中,为什么我的变量在我没有分配任何新内容的情况下被覆盖?
- mysql - 为每个用户选择一个值,即使没有 sql