I'm trying to write a custom enum.IntEnum class that overrides the starting value and adds a string attribute (discussed here). mypy doesn't like when I look up enums by value. See the two numbered lines near the bottom.
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"
The code runs fine, but mypy complains about those two lines, which look up particular enums by their integer value.
% 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"
I expect it's somehow confused about the metaclass magic in enum. The __init__() method should probably be overloaded to accept an int for these lookups, or whatever additional types I'm adding, a str in this case. Is there a way to fix these errors without # type: ignore or typing.cast()?
Update 1: I wasn't able to make those overloads explicit by adding this,
<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>
That gives me this.
% mypy goo.py
goo.py:32: error: Too many arguments for "__init__" of "object"
Update 2: Calling enum.IntEnum.__init__() instead of super().__init__() on line 32 still runs and appeases mypy, but that was arrived at by trial and error, not understanding. Also, I can't seem to even reach that line, so this "works" too. That makes it appear as though the possibility of an int is entirely artificial, there just for 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>
Is this my answer? Why?