original post: scipy/scipy-stubs#153 (comment)
Annotating a function whose return type depends on an optional positional- or keyword-only parameter in Python
Overloads are roughly similar to pattern matching1, but from the perspective of the caller.
Consider this function for example
def f(x=0, b=False):
y = (2 << x) - 1
return x if b else str(x)For simplicity let's only assume that we only accept builtin types, i.e. x: int and b: bool. The outcome depends out the literal value of b, so the output type depends on whether b is Literal[False] or Literal[True].
Because both x and b can be passed either positionally or by keyword, we're dealing with four possible call signatures:
int:f()orf(x)orf(x, False)(positional)int:f(b=False)orf(x, b=False)(keyword)str:f(b=True)orf(x, b=True)(keyword)str:f(x, True)(positional)
We can annotate each of these using overloads:
from typing import Literal as L, overload
# 1.
@overload
def f(x: int = 0, b: L[False] = False) -> int: ...
# 2. (the `*` indicates that `b` is passed as keyword argument)
@overload
def f(x: int = 0, *, b: L[False]) -> int: ...
# 3.
@overload
def f(x: int = 0, *, b: L[True]) -> str: ...
# 4. (note that `x` is required in this case, so it has no default)
@overload
def f(x: int, b: L[True]) -> str: ... Note that the second overload is actually redundant: The first one will already match f(b=False) or f(x, b=False), and both overloads have the same return type. So that leaves us with
@overload
def f(x: int = 0, b: L[False] = False) -> int: ...
@overload
def f(x: int = 0, *, b: L[True]) -> str: ...
@overload
def f(x: int, b: L[True]) -> str: ... In this case the order doesn't matter. That's because none of the overloads overlap. If you want, you could equivalently write this as e.g.
@overload
def f(x: int, b: L[True]) -> str: ...
@overload
def f(x: int = 0, *, b: L[True]) -> str: ...
@overload
def f(x: int = 0, b: L[False] = False) -> int: ...The order is just a matter of personal preference in this case. But whathever ordering you choose, try to be consistent with it.
Footnotes
-
Overloads actually arent't the same as pattern matching. But for this purpose, we can pretend that they are. ↩