We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Structural Typing and the Hard Parts step 10 of 24
@overload: one implementation, several honest signatures
dict.get has two behaviours: without a default it can return None, with
one it cannot. One signature cannot say that. Widening the return to
str | None forces every caller who did pass a default to check for a
None that can never arrive; widening to object destroys the information
entirely. @overload is how you describe both behaviours with one runtime
function.
The rules
-
At least two
@overloadvariants are required. -
Variant bodies are empty (
...). They are declarations, not code. - The implementation follows, undecorated, and is never checked against a call site. Callers are resolved against the variants only. That is the sharp edge: the place where the runtime actually lives is the one place the checker is not validating your claims.
- The implementation’s signature must be compatible with every variant. In practice its return type is the union of all the variant returns — a supertype, so it can produce each of them.
Resolution follows the typing spec’s six steps: match on arity, then on
actual argument types, then argument-type expansion, then discard
non-variadic candidates under indeterminate arguments, then check all
materializations, then take the first still standing. Order matters, and
putting a broader variant first makes the narrower ones unreachable —
overload-cannot-match.
Two anti-patterns worth naming: overloads that differ only in return
type are unsatisfiable (the checker cannot pick between them from the
arguments), and overloads used where a single type variable would do. The
spec’s own guidance is to reach for @overload only when a type variable is
insufficient.
Your task
Give fetch two variants over one implementation:
@overload
def fetch(store: Mapping[str, str], key: str) -> str | None: ...
@overload
def fetch[T](store: Mapping[str, str], key: str, default: T) -> str | T: ...
The implementation returns the stored value if the key is present, otherwise
the default (None when none was supplied).
def solve(store: dict[str, str], keys: list[str], fallback: int) -> list[str]:
For each key, append three entries in this order:
-
fetch(store, key)—"MISS"if it isNone, else.upper() -
repr(fetch(store, key, fallback))— hereTbinds toint, so the type isstr | int -
fetch(store, key, "?").lower()— hereTbinds tostr, the unionstr | strcollapses tostr, and.lower()needs no narrowing at all
Those three lines are the assertion: each one only compiles if the variant it resolves to has the return type the overloads promise.
Method decorators
When overloading a method, @staticmethod / @classmethod goes on every
variant and the implementation. @final and @override, by contrast, go
on the implementation only.
Loading visualization…
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.