James Roberts
James Roberts

Reputation: 235

enumerate causes incompatible type mypy error

The following code:

from typing import Union


def process(actions: Union[list[str], list[int]]) -> None:
    for pos, action in enumerate(actions):
        act(action)


def act(action: Union[str, int]) -> None:
    print(action)

generates a mypy error: Argument 1 to "act" has incompatible type "object"; expected "Union[str, int]"

However when removing the enumerate function the typing is fine:

from typing import Union


def process(actions: Union[list[str], list[int]]) -> None:
    for action in actions:
        act(action)


def act(action: Union[str, int]) -> None:
    print(action)

Does anyone know what the enumerate function is doing to effect the types? This is python 3.9 and mypy 0.921

Upvotes: 5

Views: 2717

Answers (3)

chepner
chepner

Reputation: 532268

enumerate.__next__ needs more context than is available to have a return type more specific than Tuple[int, Any], so I believe mypy itself would need to be modified to make the inference that enumerate(actions) produces Tuple[int,Union[str,int]] values.

Until that happens, you can explicitly cast the value of action before passing it to act.

from typing import Union, cast

StrOrInt = Union[str, int]

def process(actions: Union[list[str], list[int]]) -> None:
    for pos, action in enumerate(actions):
        act(cast(StrOrInt, action))


def act(action: Union[str, int]) -> None:
    print(action)

You can also make process generic (which now that I've thought of it, is probably a better idea, as it avoids the overhead of calling cast at runtime).

from typing import Union, cast, Iterable, TypeVar

T = TypeVar("T", str, int)

def process(actions: Iterable[T]) -> None:
    for pos, action in enumerate(actions):
        act(action)


def act(action: T) -> None:
    print(action)

Here, T is not a union of types, but a single concrete type whose identity is fixed by the call to process. Iterable[T] is either Iterable[str] or Iterable[int], depending on which type you pass to process. That fixes T for the rest of the call to process, which every call to act must take the same type of argument.

An Iterable[str] or an Iterable[int] is a valid argument, binding T to int or str in the process. Now enumerate.__next__ apparently can have a specific return type Tuple[int, T].

Upvotes: 4

Simon
Simon

Reputation: 5708

Seems like mypy isn't able to infer the type and generalizes to object. Might be worth opening an issue at their side. As a workaround you could annotate 'action'. This would remove the error. Does it work if you import the (legacy) List from typing?

Upvotes: 0

Moose
Moose

Reputation: 5

I don't know how it's affecting the types. I do know that using len() can work the same way. It is slower but if it solves the problem it might be worth it. Sorry that it's not much help

Upvotes: 0

Related Questions