Making sense of typing.overload
Make your return types more precise!
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!