1
$ cat foo.py

from typing import overload, Union, TypeVar

T = Union[int, str]
SubT = TypeVar("SubT", int, str)

@overload
def a(t: T) -> T:
    ...

@overload
def a(t: SubT) -> SubT:
    ...

def a(t: T) -> T:
    return t
$ mypy foo.py

foo.py:7: error: Overloaded function signatures 1 and 2 overlap with incompatible return types

Why are the return types incompatible, and how can I make this typecheck? I want these example return types:

v_1: Union[int, str] = 1
v_2: int = 2
v_3: str = "3"

a(v_1)  # Want Union[int, str].
a(v_2)  # Want int.
a(v_3)  # Want str.

I want to avoid having to explicitly overload a for int and str, as SubT has many more than 2 constraints in reality.

If I remove the first overload, a(v_1) wouldn't type check. If I remove the second overload, a(v_2) and a(v_3) would have their return value type hinted as Union[int, str] instead of int and str respectively.

In my actual problem, I have an Iterable of either homogeneous types under SubT (the TypeVar) or heterogeneous types under T (the Union). I want to write an a that will operate on the elements of both without losing type granulariy if it present in the homogeneous case.

Mario Ishac
  • 5,060
  • 3
  • 21
  • 52
  • 'any type of that union' IS the Union (https://docs.python.org/3/library/typing.html#typing.Union) ? – Cyril Jouve Aug 29 '20 at 21:34
  • A union and a typevar are similar. The difference is described here: https://stackoverflow.com/questions/58903906/whats-the-difference-between-a-constrained-typevar-and-a-union you very likely don't need two types and removing one would simplify your problem. – nlta Aug 29 '20 at 21:36
  • @CyrilJouve Yes, but if I operated solely on `Union` then I would lose type granularity if I know what specific type of the union `t` is. See edit. – Mario Ishac Aug 29 '20 at 21:42
  • @NathanielTracy-Amoroso Reason I think I need both is because I want to be able to support granular type checking (`TypeVar`) while at the same type supporting the broad type (`Union`). See edit. – Mario Ishac Aug 29 '20 at 21:42
  • Does `TypeVar("SubT", int, str, Union[int, str])` work in your case? (I’m not even sure if it works at all, but…) – Ry- Aug 29 '20 at 22:12
  • @Ry- That'd do exactly what I want, but unfortunately `TypeVar` constraints cannot be parameterized :( – Mario Ishac Aug 29 '20 at 22:16
  • @MarioIshac: Hm, what’s the error message in that case? It seems to be working for me: https://mypy-play.net/?mypy=latest&python=3.8&gist=06ad95655da139afd80985b02b53ba1e – Ry- Aug 29 '20 at 22:19
  • @Ry- You're right, I was mixing up your example with `TypeVar("SubT", int, str, X[int, str])` where `X` is a generic class. This wouldn't work, but `Union` is a typing construct not a class. Feel free to copy your earlier comment to an answer, I'll accept it. – Mario Ishac Aug 29 '20 at 22:29
  • Possibly relevant discussion of [mypy overload handling](https://mypy.readthedocs.io/en/stable/more_types.html#type-checking-the-variants) and of the meaning of the [overload decorator](https://github.com/python/typing/issues/253). – FMc Aug 29 '20 at 22:32

1 Answers1

1

TypeVar will let you offer both the types of the union and the union itself as options:

T = TypeVar("T", int, str, Union[int, str])

def a(t: T) -> T:
    return t
Ry-
  • 218,210
  • 55
  • 464
  • 476
  • Hmm, would even `TypeVar("T", bound=Union[int, str])` work? I'm not sure if it's exactly equivalent, but looks like it worked when I tinkered with your mypy gist. – Mario Ishac Aug 29 '20 at 22:47
  • 1
    @MarioIshac: Yeah, I think so! It does mean something slightly different (e.g. with `int` you’ll also get `bool -> bool`, as far as builtins go), but that might be okay or even what you prefer. – Ry- Aug 29 '20 at 23:05