mypy Oddity与Python枚举查找



我正在尝试编写一个自定义枚举。IntEnum类,该类重写起始值并添加字符串属性(此处讨论(mypy不喜欢按值查找枚举。请参见底部附近的两条编号线。

from __future__ import annotations
import enum
import itertools
gooCount = itertools.count(42)
class Goo(enum.IntEnum):
FOO = "fzz",
MOO = "mrp",
label: str
def __new__(cls, label: str) -> Goo:
value = next(gooCount)
mbr = int.__new__(cls, value)
mbr._value_ = value
mbr.label = label
return mbr
foo = Goo(42)                 # line 23
assert foo == 42
assert foo is Goo.FOO
assert foo.label == "fzz"
moo = Goo(43)                 # line 28
assert moo == 43
assert moo is Goo.MOO
assert moo.label == "mrp"

代码运行良好,但mypy抱怨这两行代码,它们通过整数值查找特定枚举。

% mypy goo.py
goo.py:23: error: Argument 1 to "Goo" has incompatible type "int"; expected "str"
goo.py:28: error: Argument 1 to "Goo" has incompatible type "int"; expected "str"

我想它在某种程度上对enum中的元类魔法感到困惑。__init__()方法可能应该重载以接受用于这些查找的int,或者我添加的任何其他类型,在这种情况下都是str。有没有办法在没有# type: ignoretyping.cast()的情况下修复这些错误?

更新1:我无法通过添加这个来明确这些重载,

<same as above>
from typing import Union, overload
<same as above>
class Goo(enum.IntEnum):
<same as above>
@overload
def __init__(self, arg: int) -> None: ...
@overload
def __init__(self, arg: str) -> None: ...
def __init__(self, arg: Union[int, str]) -> None:
if isinstance(arg, int):
super().__init__(arg)                       # line 32
else:
self.label = arg
<same as above>

这给了我这个。

% mypy goo.py
goo.py:32: error: Too many arguments for "__init__" of "object"

更新2:在第32行调用enum.IntEnum.__init__()而不是super().__init__()仍然运行并安抚mypy,但这是经过反复尝试而得出的,而不是理解。而且,我似乎连那条线都达不到,所以这个"作品";也这使得int的可能性看起来似乎完全是人为的,只是针对mypy

<same as above>
from typing import Union
<same as above>
class Goo(enum.IntEnum):
<same as above>
def __init__(self, arg: Union[int, str]) -> None:
assert not isinstance(arg, int)
self.label = arg
<same as above>

这是我的答案吗?为什么?

这看起来像是mypy的限制。我能找到的最好的答案是我在更新2中提到的__init__()破解,在这里重复了一遍,但全部都是。

from __future__ import annotations
import enum
import itertools
goo_count = itertools.count(42)
class Goo(enum.IntEnum):
FOO = "fzz",
MOO = "mrp",
label: str
def __new__(cls, label: str) -> Goo:
value = next(goo_count)
mbr = int.__new__(cls, value)
mbr._value_ = value
mbr.label = label
return mbr
def __init__(self, arg: int) -> None:
assert isinstance(arg, str)  # enum by-value lookups do not call __init__()
foo = Goo(42)
assert foo == 42
assert foo is Goo.FOO
assert foo.label == "fzz"
moo = Goo(43)
assert moo == 43
assert moo is Goo.MOO
assert moo.label == "mrp"

我不接受这个答案,因为这只是一个变通办法。希望mypy最终能够了解这个enums的文档用例。

最新更新