9

The below code fails mypy with error: Overloaded function signatures 1 and 2 overlap with incompatible return types.

@overload
def test_overload(x: str) -> str: ...

@overload
def test_overload(x: object) -> int: ...

def test_overload(x) -> Union[str, int]:
    if isinstance(x, str):
        return x
    else:
        return 1

What I'm trying to express is: "This function takes an arbitrary Python object. If that object is a string, it returns a string. If it is any other type, it returns an integer. Note this particular example is contrived to represent the general case.

Is it possible to express this with overloads?

Bharel
  • 23,672
  • 5
  • 40
  • 80
Sean Mackesey
  • 10,701
  • 11
  • 40
  • 66
  • Seems like you'd need to be able to express an "anything but a string" type, like the inverse of `TypeVar(bound=str)`, but I don't know of any way to do that. Is there any way you could come up with a finite set of types you might be calling this function with (even if it's very long) and make it a union type that will produce an `int` return? – Samwise Dec 17 '21 at 17:47
  • Probably, but this is a problem I run into somewhat frequently and am hoping for a better solution. – Sean Mackesey Dec 17 '21 at 17:47
  • it might be good to get a clearer idea of why you want to do this. As it is, the signatures suggest no commonality between the overloads, and I'd ask if two separate functions would be more suitable – joel Jul 06 '22 at 14:08
  • @joel in my case - decorator that acts differently if a single function is passed (thus needs to return the function signature), and on the other side accepts a variable amount of arguments, returning itself. Basically this is the way to implement it. – Bharel Jul 06 '22 at 14:51

1 Answers1

4

At the moment (Python 3.10, mypy 0.961) there is no way to express any object except one. But you could use ignoring type: ignore[misc] for excepted types. And they must precede the more general variant, because for @overload order is matter:

from typing import overload, Union


@overload
def test_overload(x: str) -> str:  # type: ignore[misc]
    ...


@overload
def test_overload(x: object) -> int:
    ...


def test_overload(x) -> Union[str, int]:
    if isinstance(x, str):
        return x
    else:
        return 1


reveal_type(test_overload("string"))  # Revealed type is "builtins.str"
reveal_type(test_overload(object()))  # Revealed type is "builtins.int"
alex_noname
  • 26,459
  • 5
  • 69
  • 86
  • 1
    Thanks. I'm having a hard time understanding why this "works" (as shown by your `reveal_type`) despite suppressing a mypy error. It's like mypy is able to correctly perform the inference but then raises an error anyway? Right now it _seems_ to me like, based on results, we have successfully been able to express "any object except one"-- but your answer says this is impossible. Could you please explain a little? – Sean Mackesey Jul 07 '22 at 15:38
  • 1
    Yes, mypy correctly infers types here despite of an error output. But I'd call it more a workaround than straight forward solution when using overloading. We are allowing some unsafety here, which you can read about [here](https://mypy.readthedocs.io/en/stable/more_types.html#type-checking-the-variants) – alex_noname Jul 07 '22 at 16:09