首页 > 解决方案 > 静态类型化枚举的抽象方法

问题描述

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

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

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当您尝试添加泛型时,该类会引发异常。

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

标签: pythonenumsabstractmypy

解决方案


部分解决方案(因为Enum不能有Genericmixin)是在抽象父方法中使用超类型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混合。


推荐阅读