How to annotate the return type which is related with input Generic type

Viewed 88
from typing import Generic, TypeVar


I = TypeVar("I")
O = TypeVar("O")


class Base(Generic[I, O]):
    def __init__(self, input: I) -> None:
        self.input = input
        
    def convert(self) -> O:
        ...

class ToStr(Base[int, str]): ...
class ToInt(Base[str, int]): ...


def convert(*c: Base) -> *O:  # I don't known how to annotate the return type
    return tuple(c.convert() for c in c)


convert(ToStr(1), ToInt("2"))  # type should be (str, int)

I am implementing a generic class, which has input generic type I and output generic type O. This class has a convert method, can convert I into O.

But when I want to define a function to call these class objects, I have no idea how to annotate the return type.

1 Answers

It looks like you've hit the nail on the head here. From what I understand of the typing module, I don't believe this is possible currently, unless someone can actually prove otherwise. The reason is that the Tuple[x, ...] syntax, which represents a variadic tuple, also known as a variable-length tuple, actually represents a homogeneous collection of elements, similar to a list which is annotated as List[x] for example.

So ostensibly, this limitation also carries over to the case when we define T as a TypeVar, and want to annotate the return type as Tuple[T, ...]. This unfortunately still means the return type is homogenous, and unfortunately it can't vary the element types depending on what the type of T is here, even though it is exactly what we desire in this case.

For example, here's a mostly thorough test I did to confirm:

from typing import Generic, TypeVar, Tuple

I = TypeVar("I")
O = TypeVar("O")


class Base(Generic[I, O]):
    def __init__(self, input: I) -> None:
        self.input = input

    def convert(self) -> O:
        ...


class ToStr(Base[int, str]): ...


class ToInt(Base[str, int]): ...


def convert(*c: Base[I, O]) -> Tuple[O, ...]:  # I don't known how to annotate the return type
    return tuple(c.convert() for c in c)


s = convert(ToStr(1), ToInt("2"))  # type should be (str, int)

# s[1].  # suggestions still show `str` as first one, which is wrong here

As you can see, that doesn't work out. The TypeVar O is bound to the type of O for the first element, str in this case.

I think, that unfortunately the closest you can get for type hinting purposes is using the below explicit syntax:

tup = ToStr(1).convert(), ToInt('2').convert()

This type checks correctly, at least in Pycharm. The IDE correctly infers the type as tuple[str, int] in this case. The downside here of course is that it's a bit unwieldy, and the syntax is a bit overly verbose. But it's the best solution I can think of at the moment, that at least does appear to work as desired.

Related