Making sense of typing.overload

Marco Gorelli
Analytics Vidhya
Published in
3 min readApr 5, 2021

Make your return types more precise!

Photo by Brett Jordan on Unsplash

What happens if you don’t use typing.overload

Suppose we have a function which take a boolean argument inplace and that its return type depends on the value of inplace. If it’s True, it returns None, else it returns an integer:

By inspecting the function, we can see that we expect the return type in line 17 to be None, while the return types in lines 18 and 19 to be Cat. However, mypy doesn’t peak inside the function, and so believes them both to be Optional[Cat]. Indeed, running the above snipped, we get:

main.py:17: note: Revealed type is 'Union[main.Cat, None]'
main.py:18: note: Revealed type is 'Union[main.Cat, None]'
main.py:19: note: Revealed type is 'Union[main.Cat, None]'
main.py:20: note: Revealed type is 'Union[main.Cat, None]'

To make the return types in lines 17 and 18 more precise, we need overload .

What happens if you do use overload

which returns

main.py:31: note: Revealed type is 'None'
main.py:32: note: Revealed type is 'main.Cat'
main.py:33: note: Revealed type is 'main.Cat'
main.py:34: note: Revealed type is 'Union[main.Cat, None]'

Note how we need overloads for:

  • Literal[False], with default
  • Literal[True]
  • bool with default

Also note that we needed to remove the return type from the implementation.

This wasn’t too bad. It gets complicated when there are extra keyword arguments before the one we’re overloading.

Overloading functions with extra arguments with defaults

Say, for example, we have:

Running mypy, we get

main.py:29: note: Revealed type is 'Union[main.Cat, None]'
main.py:30: note: Revealed type is 'Union[main.Cat, None]'
main.py:31: note: Revealed type is 'Union[main.Cat, None]'
main.py:32: note: Revealed type is 'Union[main.Cat, None]'
main.py:33: note: Revealed type is 'Union[main.Cat, None]'
main.py:34: note: Revealed type is 'Union[main.Cat, None]'
main.py:35: note: Revealed type is 'Union[main.Cat, None]'
main.py:36: note: Revealed type is 'Union[main.Cat, None]'
main.py:37: note: Revealed type is 'Union[main.Cat, None]'
main.py:38: note: Revealed type is 'Union[main.Cat, None]'
main.py:39: note: Revealed type is 'Union[main.Cat, None]'
main.py:40: note: Revealed type is 'Union[main.Cat, None]'
main.py:41: note: Revealed type is 'Union[main.Cat, None]'
main.py:42: note: Revealed type is 'Union[main.Cat, None]'
main.py:43: note: Revealed type is 'Union[main.Cat, None]'
main.py:44: note: Revealed type is 'Union[main.Cat, None]'

To make the return types more precise, we might be tempted to try that same trick as before and overload:

  • Literal[False], with default
  • Literal[True]
  • bool with default

However, this won’t work (try running the above snippet to see!). We instead need overloads of each combination of default parameter which precedes the one we want to overload.

This will return

main.py:94: note: Revealed type is 'None'
main.py:95: note: Revealed type is 'main.Cat'
main.py:96: note: Revealed type is 'main.Cat'
main.py:97: note: Revealed type is 'Union[main.Cat, None]'
main.py:98: note: Revealed type is 'None'
main.py:99: note: Revealed type is 'main.Cat'
main.py:100: note: Revealed type is 'main.Cat'
main.py:101: note: Revealed type is 'Union[main.Cat, None]'
main.py:102: note: Revealed type is 'None'
main.py:103: note: Revealed type is 'main.Cat'
main.py:104: note: Revealed type is 'main.Cat'
main.py:105: note: Revealed type is 'Union[main.Cat, None]'
main.py:106: note: Revealed type is 'None'
main.py:107: note: Revealed type is 'main.Cat'
main.py:108: note: Revealed type is 'main.Cat'
main.py:109: note: Revealed type is 'Union[main.Cat, None]'

Conclusion

We have seen how typing.overload can help us make the return types of our functions more precise. Now please, go out and overload some functions!

--

--