在 Python 类型中表达枚举及其成员之间的关系

Dav*_*vid 8 python enums mypy python-typing

如何输入(在 Python 中,例如 MyPy)一个需要两个参数的函数 - 枚举及其值/成员之一?

from enum import Enum
from typing import TypeVar, Type

class MyEnumA(Enum):
   A = 1
   B = 2

class MyEnumB(Enum):
   A = 1
   B = 2

TE = TypeVar('TE', bound=Enum)
def myfunction(member: TE, e: Type[TE]) -> None:
    pass

myfunction(MyEnumA.A, MyEnumA) # all right
myfunction(MyEnumA.A, MyEnumB) # I expect mypy-error here but it passed

print(type(MyEnumA.A)) # says: <enum 'MyEnumA'>
print(type(MyEnumB.A)) # says: <enum 'MyEnumB'>

print(f'{isinstance(MyEnumA.A, MyEnumA)=}') # says: isinstance(MyEnumA.A, MyEnumA)=True
print(f'{isinstance(MyEnumA.A, MyEnumB)=}') # says: isinstance(MyEnumA.A, MyEnumB)=False

reveal_type(MyEnumA) # mypy: Revealed type is "def (value: builtins.object) -> e.MyEnumA"
reveal_type(MyEnumA.A) # mypy: Revealed type is "Literal[e.MyEnumA.A]?"

Run Code Online (Sandbox Code Playgroud)

我想了解

  • 为什么 MyPy 在第二次调用时不报错;和
  • 如何输入myfunction以便 MyPy 检测到错误。

更多示例:

myfunction(MyEnumA.A, MyEnumA) # should pass - member of enum
myfunction(MyEnumB.A, MyEnumB) # should pass - member of enum
myfunction(MyEnumA.A, MyEnumB) # should fail - member of other enum
myfunction(MyEnumB.A, MyEnumA) # should fail - member of other enum
Run Code Online (Sandbox Code Playgroud)

Dav*_*vid 1

关键的观察是 MyPy 实际上认为类 MyEnumA 是什么:

reveal_type(MyEnumA) # mypy: Revealed type is "def (value: builtins.object) -> e.MyEnumA"
Run Code Online (Sandbox Code Playgroud)

它提供了通过以下方式进行注释的线索myfunction,无论它多么不明显:

P = ParamSpec('P')
def myfunction(member: TE, e: Callable[P, TE]) -> None:
Run Code Online (Sandbox Code Playgroud)

这使得 MyPy 在第二次调用中发现键入错误myfunction

error: Argument 2 to "myfunction" has incompatible type "Type[MyEnumA]"; expected "Callable[[object], MyEnumB]"  [arg-type]
Run Code Online (Sandbox Code Playgroud)