静态类型枚举的抽象方法

时间:2021-05-19 20:38:44

标签: python enums abstract mypy

从抽象超类枚举 ActionActivity 有继承的枚举:

  • ActivityA 可以在其上执行 ActionA;和
  • ActivityB 可以对其执行 ActionB

如何在抽象 action 方法的 perform 方法的 Activity 参数中添加类型声明,以便 MyPy 尊重继承或哪个操作适用于哪个活动?

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 类会引发异常。

如何解决这个问题以正确定义抽象方法的参数类型?

1 个答案:

答案 0 :(得分:0)

部分解决方案(因为 Enum 不能有 Generic 混合)是在抽象父方法中使用超类型 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] 显式 locally silenced 附加错误:

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 混合。