Overloaded signatures

typing · Advanced · 11 min

Many real APIs (open, dict.get, parsers) return different types depending on arguments.

Some functions return different types depending on their arguments. open(path, "rb") returns a binary file and open(path, "r") a text file. A union return type would force every caller to narrow. @overload describes each case precisely:

@overload
def load(raw: str) -> dict[str, Any]: ...
@overload
def load(raw: bytes, *, binary: Literal[True]) -> bytes: ...
def load(raw, *, binary=False):
    ...  # the real implementation

Rules:

  • Write two or more @overload-decorated signatures with ... bodies, followed by exactly one implementation without @overload.
  • The checker uses the overloads for callers and checks the implementation against all of them loosely; the implementation signature itself is not visible to callers.
  • Overloads are tried in order; the first match wins, so put specific cases first.
  • Combining with Literal makes return types depend on flag values.

At runtime, the overload definitions are replaced by the final implementation; typing.get_overloads(func) (3.11+) retrieves them for introspection.

Don't use overloads when a generic type variable expresses the relationship; def f[T](x: T) -> T is simpler than an overload per type.

Example

from typing import Literal, overload, get_overloads

@overload
def fetch(key: str, *, many: Literal[False] = False) -> str: ...
@overload
def fetch(key: str, *, many: Literal[True]) -> list[str]: ...
def fetch(key: str, *, many: bool = False) -> str | list[str]:
    values = [f"{key}-1", f"{key}-2"]
    return values if many else values[0]

one = fetch("user")              # checker: str
many = fetch("user", many=True)  # checker: list[str]
print(one.upper(), len(many))    # USER-1 2
print(len(get_overloads(fetch))) # 2

Remember

  • @overload lists the precise signatures callers see
  • Exactly one non-decorated implementation must follow
  • Literal flags let return types depend on argument values

Watch out for

  • Putting a general overload before a specific one, so the specific case is never selected.

Check yourself

  1. What runs when you call an @overload-decorated function?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/typing.html#typing.overload

Builds on

Unlocks

Nothing builds on this yet.