3
votes

In my Enum, i have defined a classmethod for coercing a given value to an Enum member. The given value may already be an instance of the Enum, or it may be a string holding an Enum value. In order to decide whether it needs conversion, i check if the argument is an instance of the class, and only pass it on to int() if it is not. At which point – according to the type hints for the argument 'item' – it must be a string.

The class look like this:

T = TypeVar('T', bound='MyEnum')

class MyEnum(Enum):
    A = 0
    B = 1

    @classmethod
    def coerce(cls: Type[T], item: Union[int, T]) -> T:
        return item if isinstance(item, cls) else cls(int(item))

mypy fails with:

error: Argument 1 to "int" has incompatible type "Union[str, T]"; expected "Union[str, bytes, SupportsInt, SupportsIndex, _SupportsTrunc]"

Why?

1

1 Answers

2
votes

It's because int cannot be constructed from Enum. From documentation of int(x)

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in radix base

So to make it work you can make your Enum inherit from str

class MyEnum(str, Enum):
    A = 0
    B = 1

Or use IntEnum

class MyEnum(IntEnum):
    A = 0
    B = 1

Edit: The question why isinstance doesn't narrow T type enough still left. Couldn't find any prove of my theory, but my (hopefully logical) explanation is - T is bounded to MyEnum so it's MyEnum and any subclass of it. If we have subclass scenario, isinstance(item, MyEnumSubclass) won't exclude case when item is just MyEnum class. If we use MyEnum directly in typing, problem will disappear.

    @classmethod
    def coerce(cls: t.Type["MyEnum"], item: t.Union[int, T]) -> "MyEnum":
        return item if isinstance(item, cls) else cls(int(item))