Overloads

In Python, it is common for callable objects to be polymorphic, meaningthey accept different types of arguments. It is also common for suchcallables to return different types depending on the arguments passed tothem. Overloads provide a way to describe the accepted input signaturesand corresponding return types.

Overload definitions

The@overload decorator allows describing functions and methodsthat support multiple different combinations of argument types. Thispattern is used frequently in builtin modules and types. For example,the__getitem__() method of thebytes type can be described asfollows:

fromtypingimportoverloadclassbytes:...@overloaddef__getitem__(self,i:int)->int:...@overloaddef__getitem__(self,s:slice)->bytes:...

This description is more precise than would be possible using unions,which cannot express the relationship between the argument and returntypes:

classbytes:...def__getitem__(self,a:int|slice)->int|bytes:...

Another example where@overload comes in handy is the type of thebuiltinmap() function, which takes a different number ofarguments depending on the type of the callable:

fromtypingimportoverloadfromcollections.abcimportCallable,Iterable,Iterator@overloaddefmap[T1,S](func:Callable[[T1],S],iter1:Iterable[T1])->Iterator[S]:...@overloaddefmap[T1,T2,S](func:Callable[[T1,T2],S],iter1:Iterable[T1],iter2:Iterable[T2],)->Iterator[S]:...# ... and we could add more items to support more than two iterables

Note that we could also easily add items to supportmap(None,...):

@overloaddefmap[T1](func:None,iter1:Iterable[T1])->Iterable[T1]:...@overloaddefmap[T1,T2](func:None,iter1:Iterable[T1],iter2:Iterable[T2],)->Iterable[tuple[T1,T2]]:...

Uses of the@overload decorator as shown above are suitable forstub files. In regular modules, a series of@overload-decorateddefinitions must be followed by exactly onenon-@overload-decorated definition (for the same function/method).The@overload-decorated definitions are for the benefit of thetype checker only, since they will be overwritten by thenon-@overload-decorated definition, while the latter is used atruntime but should be ignored by a type checker. At runtime, callingan@overload-decorated function directly will raiseNotImplementedError. Here’s an example of a non-stub overloadthat can’t easily be expressed using a union or a type variable:

@overloaddefutf8(value:None)->None:pass@overloaddefutf8(value:bytes)->bytes:pass@overloaddefutf8(value:unicode)->bytes:passdefutf8(value):<actualimplementation>

A constrainedTypeVar type can sometimes be used instead ofusing the@overload decorator. For example, the definitionsofconcat1 andconcat2 in this stub file are equivalent:

defconcat1[S:(str,bytes)](x:S,y:S)->S:...@overloaddefconcat2(x:str,y:str)->str:...@overloaddefconcat2(x:bytes,y:bytes)->bytes:...

Some functions, such asmap orbytes.__getitem__ above, can’tbe represented precisely using type variables. Werecommend that@overload is only used in cases where a typevariable is not sufficient.

Another important difference between type variables and an@overloadis that the former can also be used to define constraints for genericclass type parameters. For example, the type parameter of the genericclasstyping.IO is constrained (onlyIO[str],IO[bytes]andIO[Any] are valid):

classIO[S:(str,bytes)]:...

Invalid overload definitions

Type checkers should enforce the following rules for overload definitions.

At least two@overload-decorated definitions must be present. If onlyone is present, an error should be reported.

The@overload-decorated definitions must be followed by an overloadimplementation, which does not include an@overload decorator. Typecheckers should report an error or warning if an implementation is missing.Overload definitions within stub files, protocols, and on abstract methodswithin abstract base classes are exempt from this check.

If one overload signature is decorated with@staticmethod or@classmethod, all overload signatures must be similarly decorated. Theimplementation, if present, must also have a consistent decorator. Typecheckers should report an error if these conditions are not met.

If a@final or@override decorator is supplied for a function withoverloads, the decorator should be applied only to the overload implementationif it is present. If an overload implementation isn’t present (for example, ina stub file), the@final or@override decorator should be applied onlyto the first overload. Type checkers should enforce these rules and generatean error when they are violated. If a@final or@override decoratorfollows these rules, a type checker should treat the decorator as if it ispresent on all overloads.

Overloads are allowed to use a mixture ofasyncdef anddef statementswithin the same overload definition. Type checkers should convertasyncdef statements to a non-async signature (wrapping the returntype in aCoroutine) before testing for implementation consistency.

Implementation consistency

If an overload implementation is defined, type checkers should validatethat it is consistent with all of its associated overload signatures.The implementation should accept all potential sets of argumentsthat are accepted by the overloads and should produce all potential returntypes produced by the overloads. In typing terms, this means the inputsignature of the implementation should beassignable to the inputsignatures of all overloads, and the return type of all overloads should beassignable to the return type of the implementation.

If the implementation is inconsistent with its overloads, a type checkershould report an error:

@overloaddeffunc(x:str,/)->str:...@overloaddeffunc(x:int)->int:...# This implementation is inconsistent with the second overload# because it does not accept a keyword argument ``x`` and the# the overload's return type ``int`` is not assignable to the# implementation's return type ``str``.deffunc(x:int|str,/)->str:returnstr(x)

When a type checker checks the implementation for consistency with overloads,it should first apply any transforms that change the effective type of theimplementation including the presence of ayield statement in theimplementation body, the use ofasyncdef, and the presence of additionaldecorators.

Overload call evaluation

When a type checker evaluates the call of an overloaded function, itattempts to “match” the supplied arguments with one or more overloads.This section describes the algorithm that type checkers should usefor overload matching.

Only the overloads (the@overload-decorated signatures) should beconsidered for matching purposes. The implementation, if provided,should be ignored for purposes of overload matching.

Step 1

Examine the argument list to determine the number ofpositional and keyword arguments. Use this information to eliminate anyoverload candidates that are not plausible based on theirinput signatures.

  • If no candidate overloads remain, generate an error and stop.

  • If only one candidate overload remains, it is the winning match. Evaluateit as if it were a non-overloaded function call and stop.

  • If two or more candidate overloads remain, proceed to step 2.

Step 2

Evaluate each remaining overload as a regular (non-overloaded)call to determine whether it is compatible with the suppliedargument list. Unlike step 1, this step considers the types of the parametersand arguments. During this step, do not generate any user-visible errors.Simply record which of the overloads result in evaluation errors.

  • If all overloads result in errors, proceed to step 3.

  • If only one overload evaluates without error, it is the winning match.Evaluate it as if it were a non-overloaded function call and stop.

  • If two or more candidate overloads remain, proceed to step 4.

Step 3

If step 2 produces errors for all overloads, perform“argument type expansion”. Union types can be expandedinto their constituent subtypes. For example, the typeint|str canbe expanded intoint andstr.

Type expansion should be performed one argument at a time from left toright. Each expansion results in sets of effective argument types.For example, if there are two arguments whose types evaluate toint|str andint|bytes, expanding the first argument typeresults in two sets of argument types:(int,int|bytes) and(str,int|bytes). If type expansion for the second argument is required,four sets of argument types are produced:(int,int),(int,bytes),(str,int), and(str,bytes).

After each argument’s expansion, return to step 2 and evaluate allexpanded argument lists.

  • If all argument lists evaluate successfully, combine theirrespective return types by union to determine the final return typefor the call, and stop.

  • If argument expansion has been applied to all arguments and one ormore of the expanded argument lists cannot be evaluated successfully,generate an error and stop.

For additional details about argument type expansion, seeargument-type-expansion below.

Step 4

If the argument list is compatible with two or more overloads,determine whether one or more of the overloads has a variadic parameter(either*args or**kwargs) that maps to a corresponding argumentthat supplies an indeterminate number of positional or keyword arguments.If so, eliminate overloads that do not have a variadic parameter.

  • If this results in only one remaining candidate overload, it isthe winning match. Evaluate it as if it were a non-overloaded functioncall and stop.

  • If two or more candidate overloads remain, proceed to step 5.

Step 5

For all arguments, determine whether all possiblematerializations of the argument’s type are assignable tothe corresponding parameter type for each of the remaining overloads. If so,eliminate all of the subsequent remaining overloads.

Consider the following example:

@overloaddefexample(x:list[int])->int:...@overloaddefexample(x:list[Any])->str:...@overloaddefexample(x:Any)->Any:...deftest(a:list[Any]):# All materializations of list[Any] will match either the first or# second overload, so the third overload can be eliminated.example(a)

This rule eliminates overloads that will never be chosen even if thecaller eliminates types that includeAny.

If the call involves more than one argument, all possible materializations ofevery argument type must be assignable to its corresponding parameter type.If this condition exists, all subsequent remaining overloads should be eliminated.

Once this filtering process is applied for all arguments, examine the returntypes of the remaining overloads. If these return types include type variables,they should be replaced with their solved types. If the resulting return typesfor all remaining overloads areequivalent, proceed to step 6.

If the return types are not equivalent, overload matching is ambiguous. Inthis case, assume a return type ofAny and stop.

Step 6

Choose the first remaining candidate overload as the winningmatch. Evaluate it as if it were a non-overloaded function call and stop.

Examples

Example 1:

@overloaddefexample1(x:int,y:str)->int:...@overloaddefexample1(x:str)->str:...example1()# Error in step 1: no plausible overloadsexample1(1,"")# Step 1 eliminates second overloadexample1("")# Step 1 eliminates first overloadexample1("","")# Error in step 2: no compatible overloadsexample1(1)# Error in step 2: no compatible overloads

Example 2:

@overloaddefexample2(x:int,y:str,z:int)->str:...@overloaddefexample2(x:int,y:int,z:int)->int:...deftest(values:list[str|int]):# In this example, argument type expansion is# performed on the first two arguments. Expansion# of the third is unnecessary.r1=example2(1,values[0],1)reveal_type(r1)# Should reveal str | int# Here, the types of all three arguments are expanded# without success.example2(values[0],values[1],values[2])# Error in step 3

Example 3:

@overloaddefexample3(x:int,/)->tuple[int]:...@overloaddefexample3(x:int,y:int,/)->tuple[int,int]:...@overloaddefexample3(*args:int)->tuple[int,...]:...deftest(val:list[int]):# Step 1 eliminates second overload. Step 4 and# step 5 do not apply. Step 6 picks the first# overload.r1=example3(1)reveal_type(r1)# Should reveal tuple[int]# Step 1 eliminates first overload. Step 4 and# step 5 do not apply. Step 6 picks the second# overload.r2=example3(1,2)reveal_type(r2)# Should reveal tuple[int, int]# Step 1 doesn't eliminate any overloads. Step 4# picks the third overload.r3=example3(*val)reveal_type(r3)# Should reveal tuple[int, ...]

Example 4:

@overloaddefexample4(x:list[int],y:int)->int:...@overloaddefexample4(x:list[str],y:str)->int:...@overloaddefexample4(x:int,y:int)->list[int]:...deftest(v1:list[Any],v2:Any):# Step 2 eliminates the third overload. Step 5# determines that first and second overloads# both apply and are ambiguous due to Any, but# return types are consistent.r1=example4(v1,v2)reveal_type(r1)# Reveals int# Step 2 eliminates the second overload. Step 5# determines that first and third overloads# both apply and are ambiguous due to Any, and# the return types are inconsistent.r2=example4(v2,1)reveal_type(r2)# Should reveal Any

Argument type expansion

When performing argument type expansion, a type that is equivalent toa union of a finite set of subtypes should be expanded into its constituentsubtypes. This includes the following cases.

  1. Explicit unions: Each subtype of the union should be considered as aseparate argument type. For example, the typeint|str should be expandedintoint andstr.

  2. bool should be expanded intoLiteral[True] andLiteral[False].

  3. Enum types (other than those that derive fromenum.Flag) shouldbe expanded into their literal members.

  4. type[A|B] should be expanded intotype[A] andtype[B].

  5. Tuples of known length that contain expandable types should be expandedinto all possible combinations of their element types. For example, the typetuple[int|str,bool] should be expanded into(int,Literal[True]),(int,Literal[False]),(str,Literal[True]), and(str,Literal[False]).

The above list may not be exhaustive, and additional cases may be added inthe future as the type system evolves.