21

The example code of what I am trying to ask is below.
None of the examples on the internet try to overload argument value as such.
One of the argument is a bool value and I want to overload a method based on the bool value rather than the usual, argument type.

from typing import overload, Union

@overload
def myfunc(arg: bool = True)-> str: ...

@overload
def myfunc(arg: bool = False) -> int: ...

def myfunc(arg: bool) -> Union[int, str]:
  if arg:
    return "something"
  else:
    return 0

Is the overloading code in the above example code correct ?
Can you give an example/blog/source that mentions this kind of overloading, since I couldn't find anything in Python docs and pep-484

I found one probable way of doing it is with typing.Literal as used in latest python docs (since python v3.8)

from typing import overload, Union, Literal

@overload
def myfunc(arg: Literal[True]) -> str: ...

@overload
def myfunc(arg: Literal[False]) -> int: ...

def myfunc(arg: bool) -> Union[int, str]:
  if arg:
    return "something"
  else:
    return 0

But I cannot move to python 3.8 just yet as I am working on production code that is still on python 3.6, soon upgrading to 3.7 at best.
Hence I am still looking for answers on how to achieve this in python 3.6

Augustin
  • 2,444
  • 23
  • 24
dumbPy
  • 1,379
  • 1
  • 6
  • 19

2 Answers2

22

Install the typing-extensions module which contains official backports of various typing constructs. Then, do:

from typing import overload, Union

# typing_extensions defines Literal for Python 3.7 and earlier, but
# re-exports it from 'typing' for later versions of Python.
from typing_extensions import Literal

@overload
def myfunc(arg: Literal[True]) -> str: ...

@overload
def myfunc(arg: Literal[False]) -> int: ...

@overload
def myfunc(arg: bool) -> Union[str, int]: ...

def myfunc(arg: bool) -> Union[int, str]:
    if arg: return "something"
    else: return 0

See the first example in the mypy docs on literal types for why I included the third overload on bool.

Michael0x2a
  • 58,192
  • 30
  • 175
  • 224
  • Thank You !!! @erictraut answered this in pyright issue I created of the same [here](https://github.com/microsoft/pyright/issues/428#issuecomment-566158408) "You're on the right track. You need to use Literal[True] and Literal[False]. You can work around the need for Python 3.8 by importing Literal from typing_extensions and placing the annotation in quotes. This will prevent the python interpreter from evaluating the type at runtime." – dumbPy Dec 17 '19 at 06:37
13

Building off the correct accepted answer, @overloading for default (keyword) arguments can be achieved as follows:

from typing import overload, Union, Literal

@overload
def myfunc(arg: Literal[True] = True) -> str: ...

@overload
def myfunc(arg: Literal[False]) -> int: ...

def myfunc(arg: bool =  True) -> Union[int, str]:
    if arg: return "something"
    else: return 0

The first overload (with Literal[True]) is used for type-checking function calls myfunc(True) and myfunc(), while the second overload (with Literal[False]) is used for type-checking the function call myfunc(False).

Jasha
  • 5,507
  • 2
  • 33
  • 44
  • How does this improve upon the accepted answer? – SteveJ Jan 06 '21 at 16:31
  • 6
    This example shows how to type a function that supplies a default argument, which is not covered in the accepted answer. In particular, the pattern above enables typecheckers like mypy to perform type inference on a call to `myfunc` that is made without any arguments, e.g. `x = myfunc()`. – Jasha Jan 06 '21 at 22:51
  • 2
    At first, I thought you didn't need the type annotations for the actual/non-overloaded function definition. But actually, you should include them, because otherwise you could get a type wrong in an overload and it wouldn't be able to tell - it would treat all args as `Any`. This doesn't seem to have been nailed down the way it ought to be - https://github.com/python/mypy/issues/8867#issuecomment-767842357 – binaryfunt Apr 30 '21 at 17:41
  • 1
    Also: as mentioned in the other answer, a third overload with identical args & annotations to the actual function definition is needed. Hopefully this will be fixed soon https://github.com/python/mypy/issues/6113 – binaryfunt Jun 28 '21 at 16:31
  • 2
    How would this work if the `arg` argument is preceded by another argument with a default. For example: ```python def myfunc(some_arg: int = 0, arg: bool = True) -> int | str: ``` In that case, `mypy` will raise the error `non-default argument follows default argument [syntax]` for the second overload definition since it doesn't specify a default. However, actually specifying the default as ```python @overload def myfunc(some_arg: int = 0, arg: Literal[False] = False) -> str: ... ``` will print `Overloaded function signatures 1 and 2 overlap with incompatible return types [misc]` – Pankrates May 02 '23 at 14:49
  • @Pankrates in that case you need to use three overloads: 1. `@overload def myfunc(int_arg: int = 0, as_str: Literal[True] = True) -> str: ...`, 2. `@overload def myfunc(int_arg: int = 0, *, as_str: Literal[False]) -> int: ...`, 3. `@overload def myfunc(int_arg: int, as_str: Literal[False]) -> int: ...`. – Jasha May 03 '23 at 07:39