(Some similarities to Make `@overload` less verbose here, but a simpler variation, since each overload is still required to list all the parameter names, it just doesn’t have to repeat the annotations if they have no bearing on how the overload resolves. This simplification means the proposal is already valid Python syntax, and hence would only require updates to static type checkers, allowing it to be used on all currently supported Python versions, rather than being restricted to 3.14+ or 3.15+)
For a current project, I needed to overload a bunch of APIs with several optional parameters, but only one of them influenced the returned type (if it’s None
, you get Result[str]
, if it’s set you get Result[Mapping[str, Any]]
).
The overload syntax allows default values to be omitted from the overload definitions by specifying them as ...
.
It would be nice to be able to eliminate duplicated parameter annotations in the same way: param_name: ...
would mean “required parameter with the same annotation as the implementation function”, and param_name: ... = ...
would mean “optional parameter with the same annotation and default value as the implementation function”.
That way overloads would only need to spell out the parameters that actually influence the result type:
@overload
def f(relevant_param: None, required: ..., optional: ... = ...) -> Result[str]: ...
@overload
def f(relevant_param: FieldSpec, required: ..., optional: ... = ...) -> Result[Mapping[str, Any]]: ...
def f(relevant_param: FieldSpec|None, required: SomeType, optional: SomeOtherType|None = None) -> Result[str] | Result[Mapping[str, Any]:
...
Rather than having to repeat all the annotations, even when they don’t affect the overload resolution:
@overload
def f(relevant_param: None, required: SomeType, optional: SomeOtherType|None = ...) -> Result[str]: ...
@overload
def f(relevant_param: FieldSpec, required: SomeType, optional: SomeOtherType|None = ...) -> Result[Mapping[str, Any]]: ...
def f(relevant_param: FieldSpec|None, required: SomeType, optional: SomeOtherType|None = None) -> Result[str] | Result[Mapping[str, Any]:
...
At runtime, the overloads are overwritten by the implementation definition anyway, so there wouldn’t be any change to runtime annotations here.
Type checkers are already able to detect overload inconsistencies with each other and with the implementation functions, so presumably they’re analysing them collectively and would be able to fill in the omitted annotations from the implementation function before continuing with the rest of their work.
Edit: as per @TeamSpen210’s comment below, to support type stubs and protocols, rather than the ...
resolution coming specifically from the “implementation function”, it would instead need to come from something like the “implementation function, or the last listed overload when there is no implementation function provided”. This is a slightly narrower definition than Spencer suggested, but it aims to ensure the runtime annotations on protocol definitions aren’t allowed to contain an unresolved ellipsis (and similarly for implementation function annotations).