Search code examples
pythonpython-typing

Typing with TypeVar converts a type to an object


I am trying to implement a generator that will return a pair of a sequence element and a boolean value indicating whether the element is the last one.

from collections.abc import Generator, Iterable
from itertools import chain, tee
from typing import TypeVar

_T1 = TypeVar('_T1')
_MISSING = object()


def pairwise(iterable: Iterable[_T1]) -> Iterable[tuple[_T1, _T1]]:
    # See https://docs.python.org/3.9/library/itertools.html#itertools-recipes
    a, b = tee(iterable)
    next(b, None)
    return zip(a, b)


def annotated_last(sequence: Iterable[_T1]) -> Generator[tuple[_T1, bool], Any, None]:
    for current_item, next_item in pairwise(chain(sequence, [_MISSING])):
        is_last = next_item is _MISSING
        yield current_item, is_last  # <-- mypy error

However, mypy returns this error:

Incompatible types in "yield" (actual type "tuple[object, bool]", expected type "tuple[_T1, bool]")

Tell me how to correctly annotate types in these functions.

I am using Python version 3.9.19


Solution

  • This is because you created a chain iterator like so: chain(sequence, [_MISSING]), and the type inference has to infer the most generic type from these arguments, but _MISSING is object, so it has to be an iterator of object. Note, you can implement the function you want with the signature you want straightforwardly (albeit, less elegantly) doing something like:

    from collections.abc import Iterator, Iterable
    from typing import TypeVar
    
    _T1 = TypeVar('_T1')
    
    
    def annotated_last(sequence: Iterable[_T1]) -> Iterator[tuple[_T1, bool]]:
        it = iter(sequence)
        try:
            previous = next(it)
        except StopIteration:
            return
        for current in it:
            yield previous, False
            previous = current
        yield previous, True