First steps
Type system reference
Configuring and running mypy
Miscellaneous
Project Links
This section introduces a few additional kinds of types, includingNoReturn,NewType, and types for async code. It also discusseshow to give functions more precise types using overloads. All of these are onlysituationally useful, so feel free to skip this section and come back when youhave a need for some of them.
Here’s a quick summary of what’s covered here:
NoReturn lets you tell mypy that a function never returns normally.
NewType lets you define a variant of a type that is treated as aseparate type by mypy but is identical to the original type at runtime.For example, you can haveUserId as a variant ofint that isjust anint at runtime.
@overload lets you define a function that can accept multiple distinctsignatures. This is useful if you need to encode a relationship between thearguments and the return type that would be difficult to express normally.
Async types let you type check programs usingasync andawait.
Mypy provides support for functions that never return. Forexample, a function that unconditionally raises an exception:
fromtypingimportNoReturndefstop()->NoReturn:raiseException('no way')
Mypy will ensure that functions annotated as returningNoReturntruly never return, either implicitly or explicitly. Mypy will alsorecognize that the code after calls to such functions is unreachableand will behave accordingly:
deff(x:int)->int:ifx==0:returnxstop()return'whatever works'# No error in an unreachable block
In earlier Python versions you need to installtyping_extensions usingpip to useNoReturn in your code. Python 3 command line:
python3 -m pip install --upgrade typing-extensions
There are situations where you may want to avoid programming errors bycreating simple derived classes that are only used to distinguishcertain values from base class instances. Example:
classUserId(int):passdefget_by_user_id(user_id:UserId):...
However, this approach introduces some runtime overhead. To avoid this, the typingmodule provides a helper objectNewType that creates simple unique types withalmost zero runtime overhead. Mypy will treat the statementDerived=NewType('Derived',Base) as being roughly equivalent to the followingdefinition:
classDerived(Base):def__init__(self,_x:Base)->None:...
However, at runtime,NewType('Derived',Base) will return a dummy callable thatsimply returns its argument:
defDerived(_x):return_x
Mypy will require explicit casts fromint whereUserId is expected, whileimplicitly casting fromUserId whereint is expected. Examples:
fromtypingimportNewTypeUserId=NewType('UserId',int)defname_by_id(user_id:UserId)->str:...UserId('user')# Fails type checkname_by_id(42)# Fails type checkname_by_id(UserId(42))# OKnum:int=UserId(5)+1
NewType accepts exactly two arguments. The first argument must be a string literalcontaining the name of the new type and must equal the name of the variable to which the newtype is assigned. The second argument must be a properly subclassable class, i.e.,not a type construct like aunion type, etc.
The callable returned byNewType accepts only one argument; this is equivalent tosupporting only one constructor accepting an instance of the base class (see above).Example:
fromtypingimportNewTypeclassPacketId:def__init__(self,major:int,minor:int)->None:self._major=majorself._minor=minorTcpPacketId=NewType('TcpPacketId',PacketId)packet=PacketId(100,100)tcp_packet=TcpPacketId(packet)# OKtcp_packet=TcpPacketId(127,0)# Fails in type checker and at runtime
You cannot useisinstance() orissubclass() on the object returned byNewType, nor can you subclass an object returned byNewType.
Note
Unlike type aliases,NewType will create an entirely new andunique type when used. The intended purpose ofNewType is to help youdetect cases where you accidentally mixed together the old base type and thenew derived type.
For example, the following will successfully typecheck when using typealiases:
UserId=intdefname_by_id(user_id:UserId)->str:...name_by_id(3)# ints and UserId are synonymous
But a similar example usingNewType will not typecheck:
fromtypingimportNewTypeUserId=NewType('UserId',int)defname_by_id(user_id:UserId)->str:...name_by_id(3)# int is not the same as UserId
Sometimes the arguments and types in a function depend on each otherin ways that can’t be captured with aunion types. For example, supposewe want to write a function that can accept x-y coordinates. If we passin just a single x-y coordinate, we return aClickEvent object. However,if we pass in two x-y coordinates, we return aDragEvent object.
Our first attempt at writing this function might look like this:
defmouse_event(x1:int,y1:int,x2:int|None=None,y2:int|None=None)->ClickEvent|DragEvent:ifx2isNoneandy2isNone:returnClickEvent(x1,y1)elifx2isnotNoneandy2isnotNone:returnDragEvent(x1,y1,x2,y2)else:raiseTypeError("Bad arguments")
While this function signature works, it’s too loose: it impliesmouse_eventcould return either object regardless of the number of argumentswe pass in. It also does not prohibit a caller from passing in the wrongnumber of ints: mypy would treat calls likemouse_event(1,2,20) as beingvalid, for example.
We can do better by usingoverloadingwhich lets us give the same function multiple type annotations (signatures)to more accurately describe the function’s behavior:
fromtypingimportoverload# Overload *variants* for 'mouse_event'.# These variants give extra information to the type checker.# They are ignored at runtime.@overloaddefmouse_event(x1:int,y1:int)->ClickEvent:...@overloaddefmouse_event(x1:int,y1:int,x2:int,y2:int)->DragEvent:...# The actual *implementation* of 'mouse_event'.# The implementation contains the actual runtime logic.## It may or may not have type hints. If it does, mypy# will check the body of the implementation against the# type hints.## Mypy will also check and make sure the signature is# consistent with the provided variants.defmouse_event(x1:int,y1:int,x2:int|None=None,y2:int|None=None)->ClickEvent|DragEvent:ifx2isNoneandy2isNone:returnClickEvent(x1,y1)elifx2isnotNoneandy2isnotNone:returnDragEvent(x1,y1,x2,y2)else:raiseTypeError("Bad arguments")
This allows mypy to understand calls tomouse_event much more precisely.For example, mypy will understand thatmouse_event(5,25) willalways have a return type ofClickEvent and will report errors forcalls likemouse_event(5,25,2).
As another example, suppose we want to write a custom container class thatimplements the__getitem__ method ([] bracket indexing). If thismethod receives an integer we return a single item. If it receives aslice, we return aSequence of items.
We can precisely encode this relationship between the argument and thereturn type by using overloads like so (Python 3.12 syntax):
fromcollections.abcimportSequencefromtypingimportoverloadclassMyList[T](Sequence[T]):@overloaddef__getitem__(self,index:int)->T:...@overloaddef__getitem__(self,index:slice)->Sequence[T]:...def__getitem__(self,index:int|slice)->T|Sequence[T]:ifisinstance(index,int):# Return a T hereelifisinstance(index,slice):# Return a sequence of Ts hereelse:raiseTypeError(...)
Here is the same example using the legacy syntax (Python 3.11 and earlier):
fromcollections.abcimportSequencefromtypingimportTypeVar,overloadT=TypeVar('T')classMyList(Sequence[T]):@overloaddef__getitem__(self,index:int)->T:...@overloaddef__getitem__(self,index:slice)->Sequence[T]:...def__getitem__(self,index:int|slice)->T|Sequence[T]:ifisinstance(index,int):# Return a T hereelifisinstance(index,slice):# Return a sequence of Ts hereelse:raiseTypeError(...)
Note
If you just need to constrain a type variable to certain types orsubtypes, you can use avalue restriction.
The default values of a function’s arguments don’t affect its signature – onlythe absence or presence of a default value does. So in order to reduceredundancy, it’s possible to replace default values in overload definitions with... as a placeholder:
fromtypingimportoverloadclassM:...@overloaddefget_model(model_or_pk:M,flag:bool=...)->M:...@overloaddefget_model(model_or_pk:int,flag:bool=...)->M|None:...defget_model(model_or_pk:int|M,flag:bool=True)->M|None:...
An overloaded function must consist of two or more overloadvariantsfollowed by animplementation. The variants and the implementationsmust be adjacent in the code: think of them as one indivisible unit.
The variant bodies must all be empty; only the implementation is allowedto contain code. This is because at runtime, the variants are completelyignored: they’re overridden by the final implementation function.
This means that an overloaded function is still an ordinary Pythonfunction! There is no automatic dispatch handling and you must manuallyhandle the different types in the implementation (e.g. by usingif statements andisinstance checks).
If you are adding an overload within a stub file, the implementationfunction should be omitted: stubs do not contain runtime logic.
Note
While we can leave the variant body empty using thepass keyword,the more common convention is to instead use the ellipsis (...) literal.
When you call an overloaded function, mypy will infer the correct returntype by picking the best matching variant, after taking into considerationboth the argument types and arity. However, a call is never typechecked against the implementation. This is why mypy will report callslikemouse_event(5,25,3) as being invalid even though it matches theimplementation signature.
If there are multiple equally good matching variants, mypy will selectthe variant that was defined first. For example, consider the followingprogram:
# For Python 3.8 and below you must use `typing.List` instead of `list`. e.g.# from typing import Listfromtypingimportoverload@overloaddefsummarize(data:list[int])->float:...@overloaddefsummarize(data:list[str])->str:...defsummarize(data):ifnotdata:return0.0elifisinstance(data[0],int):# Do int specific codeelse:# Do str-specific code# What is the type of 'output'? float or str?output=summarize([])
Thesummarize([]) call matches both variants: an empty list couldbe either alist[int] or alist[str]. In this case, mypywill break the tie by picking the first matching variant:outputwill have an inferred type offloat. The implementer is responsiblefor making suresummarize breaks ties in the same way at runtime.
However, there are two exceptions to the “pick the first match” rule.First, if multiple variants match due to an argument being of typeAny, mypy will make the inferred type also beAny:
dynamic_var:Any=some_dynamic_function()# output2 is of type 'Any'output2=summarize(dynamic_var)
Second, if multiple variants match due to one or more of the argumentsbeing a union, mypy will make the inferred type be the union of thematching variant returns:
some_list:list[int]|list[str]# output3 is of type 'float | str'output3=summarize(some_list)
Note
Due to the “pick the first match” rule, changing the order of youroverload variants can change how mypy type checks your program.
To minimize potential issues, we recommend that you:
Make sure your overload variants are listed in the same order asthe runtime checks (e.g.isinstance checks) in your implementation.
Order your variants and runtime checks from most to least specific.(See the following section for an example).
Mypy will perform several checks on your overload variant definitionsto ensure they behave as expected. First, mypy will check and make surethat no overload variant is shadowing a subsequent one. For example,consider the following function which adds together twoExpressionobjects, and contains a special-case to handle receiving twoLiteraltypes:
fromtypingimportoverloadclassExpression:# ...snip...classLiteral(Expression):# ...snip...# Warning -- the first overload variant shadows the second!@overloaddefadd(left:Expression,right:Expression)->Expression:...@overloaddefadd(left:Literal,right:Literal)->Literal:...defadd(left:Expression,right:Expression)->Expression:# ...snip...
While this code snippet is technically type-safe, it does contain ananti-pattern: the second variant will never be selected! If we try callingadd(Literal(3),Literal(4)), mypy will always pick the first variantand evaluate the function call to be of typeExpression, notLiteral.This is becauseLiteral is a subtype ofExpression, which meansthe “pick the first match” rule will always halt after considering thefirst overload.
Because having an overload variant that can never be matched is almostcertainly a mistake, mypy will report an error. To fix the error, we caneither 1) delete the second overload or 2) swap the order of the overloads:
# Everything is ok now -- the variants are correctly ordered# from most to least specific.@overloaddefadd(left:Literal,right:Literal)->Literal:...@overloaddefadd(left:Expression,right:Expression)->Expression:...defadd(left:Expression,right:Expression)->Expression:# ...snip...
Mypy will also type check the different variants and flag any overloadsthat have inherently unsafely overlapping variants. For example, considerthe following unsafe overload definition:
fromtypingimportoverload@overloaddefunsafe_func(x:int)->int:...@overloaddefunsafe_func(x:object)->str:...defunsafe_func(x:object)->int|str:ifisinstance(x,int):return42else:return"some string"
On the surface, this function definition appears to be fine. However, it willresult in a discrepancy between the inferred type and the actual runtime typewhen we try using it like so:
some_obj:object=42unsafe_func(some_obj)+" danger danger"# Type checks, yet crashes at runtime!
Sincesome_obj is of typeobject, mypy will decide thatunsafe_funcmust return something of typestr and concludes the above will type check.But in reality,unsafe_func will return an int, causing the code to crashat runtime!
To prevent these kinds of issues, mypy will detect and prohibit inherently unsafelyoverlapping overloads on a best-effort basis. Two variants are considered unsafelyoverlapping when both of the following are true:
All of the arguments of the first variant are potentially compatible with the second.
The return type of the first variant isnot compatible with (e.g. is not asubtype of) the second.
So in this example, theint argument in the first variant is a subtype oftheobject argument in the second, yet theint return type is not a subtype ofstr. Both conditions are true, so mypy will correctly flagunsafe_func asbeing unsafe.
Note that in cases where you ignore the overlapping overload error, mypy will usuallystill infer the types you expect at callsites.
However, mypy will not detectall unsafe uses of overloads. For example,suppose we modify the above snippet so it callssummarize instead ofunsafe_func:
some_list:list[str]=[]summarize(some_list)+"danger danger"# Type safe, yet crashes at runtime!
We run into a similar issue here. This program type checks if we look just at theannotations on the overloads. But sincesummarize(...) is designed to be biasedtowards returning a float when it receives an empty list, this program will actuallycrash during runtime.
The reason mypy does not flag definitions likesummarize as being potentiallyunsafe is because if it did, it would be extremely difficult to write a safeoverload. For example, suppose we define an overload with two variants that accepttypesA andB respectively. Even if those two types were completely unrelated,the user could still potentially trigger a runtime error similar to the ones above bypassing in a value of some third typeC that inherits from bothA andB.
Thankfully, these types of situations are relatively rare. What this does mean,however, is that you should exercise caution when designing or using an overloadedfunction that can potentially receive values that are an instance of two seeminglyunrelated types.
The body of an implementation is type-checked against thetype hints provided on the implementation. For example, in theMyList example up above, the code in the body is checked withargument listindex:int|slice and a return type ofT|Sequence[T]. If there are no annotations on theimplementation, then the body is not type checked. If you want toforce mypy to check the body anyways, use the--check-untyped-defsflag (more details here).
The variants must also also be compatible with the implementationtype hints. In theMyList example, mypy will check that theparameter typeint and the return typeT are compatible withint|slice andT|Sequence for thefirst variant. For the second variant it verifies the parametertypeslice and the return typeSequence[T] are compatiblewithint|slice andT|Sequence.
Note
The overload semantics documented above are new as of mypy 0.620.
Previously, mypy used to perform type erasure on all overload variants. Forexample, thesummarize example from the previous section used to beillegal becauselist[str] andlist[int] both erased to justlist[Any].This restriction was removed in mypy 0.620.
Mypy also previously used to select the best matching variant using a differentalgorithm. If this algorithm failed to find a match, it would default to returningAny. The new algorithm uses the “pick the first match” rule and will fall backto returningAny only if the input arguments also containAny.
Sometimes it is useful to define overloads conditionally.Common use cases include types that are unavailable at runtime or thatonly exist in a certain Python version. All existing overload rules still apply.For example, there must be at least two overloads.
Note
Mypy can only infer a limited number of conditions.Supported ones currently includeTYPE_CHECKING,MYPY,Python version and system platform checks,--always-true,and--always-false values.
fromtypingimportTYPE_CHECKING,Any,overloadifTYPE_CHECKING:classA:...classB:...ifTYPE_CHECKING:@overloaddeffunc(var:A)->A:...@overloaddeffunc(var:B)->B:...deffunc(var:Any)->Any:returnvarreveal_type(func(A()))# Revealed type is "A"
# flags: --python-version 3.10importsysfromtypingimportAny,overloadclassA:...classB:...classC:...classD:...ifsys.version_info<(3,7):@overloaddeffunc(var:A)->A:...elifsys.version_info>=(3,10):@overloaddeffunc(var:B)->B:...else:@overloaddeffunc(var:C)->C:...@overloaddeffunc(var:D)->D:...deffunc(var:Any)->Any:returnvarreveal_type(func(B()))# Revealed type is "B"reveal_type(func(C()))# No overload variant of "func" matches argument type "C"# Possible overload variants:# def func(var: B) -> B# def func(var: D) -> D# Revealed type is "Any"
Note
In the last example, mypy is executed with--python-version3.10.Therefore, the conditionsys.version_info>=(3,10) will match andthe overload forB will be added.The overloads forA andC are ignored!The overload forD is not defined conditionally and thus is also added.
When mypy cannot infer a condition to be alwaysTrue or alwaysFalse,an error is emitted.
fromtypingimportAny,overloadclassA:...classB:...defg(bool_var:bool)->None:ifbool_var:# Condition can't be inferred, unable to merge overloads@overloaddeffunc(var:A)->A:...@overloaddeffunc(var:B)->B:...deffunc(var:Any)->Any:...reveal_type(func(A()))# Revealed type is "Any"
Normally, mypy doesn’t require annotations for the first arguments of instance andclass methods. However, they may be needed to have more precise static typingfor certain programming patterns.
In generic classes some methods may be allowed to be called onlyfor certain values of type arguments (Python 3.12 syntax):
classTag[T]:item:Tdefuppercase_item(self:Tag[str])->str:returnself.item.upper()deflabel(ti:Tag[int],ts:Tag[str])->None:ti.uppercase_item()# E: Invalid self argument "Tag[int]" to attribute function# "uppercase_item" with type "Callable[[Tag[str]], str]"ts.uppercase_item()# This is OK
This pattern also allows matching on nested types in situations where the typeargument is itself generic (Python 3.12 syntax):
fromcollections.abcimportSequenceclassStorage[T]:def__init__(self,content:T)->None:self._content=contentdeffirst_chunk[S](self:Storage[Sequence[S]])->S:returnself._content[0]page:Storage[list[str]]page.first_chunk()# OK, type is "str"Storage(0).first_chunk()# Error: Invalid self argument "Storage[int]" to attribute function# "first_chunk" with type "Callable[[Storage[Sequence[S]]], S]"
Finally, one can use overloads on self-type to express precise types ofsome tricky methods (Python 3.12 syntax):
fromcollections.abcimportCallablefromtypingimportoverloadclassTag[T]:@overloaddefexport(self:Tag[str])->str:...@overloaddefexport(self,converter:Callable[[T],str])->str:...defexport(self,converter=None):ifisinstance(self.item,str):returnself.itemreturnconverter(self.item)
In particular, an__init__() method overloaded on self-typemay be useful to annotate generic class constructors where type argumentsdepend on constructor parameters in a non-trivial way, see e.g.Popen.
Using host class protocol as a self-type in mixin methods allowsmore code re-usability for static typing of mixin classes. For example,one can define a protocol that defines common functionality forhost classes instead of adding required abstract methods to every mixin:
classLockable(Protocol):@propertydeflock(self)->Lock:...classAtomicCloseMixin:defatomic_close(self:Lockable)->int:withself.lock:# perform actionsclassAtomicOpenMixin:defatomic_open(self:Lockable)->int:withself.lock:# perform actionsclassFile(AtomicCloseMixin,AtomicOpenMixin):def__init__(self)->None:self.lock=Lock()classBad(AtomicCloseMixin):passf=File()b:Badf.atomic_close()# OKb.atomic_close()# Error: Invalid self type for "atomic_close"
Note that the explicit self-type isrequired to be a protocol whenever itis not a supertype of the current class. In this case mypy will check the validityof the self-type only at the call site.
Some classes may define alternative constructors. If theseclasses are generic, self-type allows giving them precisesignatures (Python 3.12 syntax):
fromtypingimportSelfclassBase[T]:def__init__(self,item:T)->None:self.item=item@classmethoddefmake_pair(cls,item:T)->tuple[Self,Self]:returncls(item),cls(item)classSub[T](Base[T]):...pair=Sub.make_pair('yes')# Type is "tuple[Sub[str], Sub[str]]"bad=Sub[int].make_pair('no')# Error: Argument 1 to "make_pair" of "Base"# has incompatible type "str"; expected "int"
Mypy lets you type coroutines that use theasync/await syntax.For more information regarding coroutines, seePEP 492 and theasyncio documentation.
Functions defined usingasyncdef are typed similar to normal functions.The return type annotation should be the same as the type of the value youexpect to get back whenawait-ing the coroutine.
importasyncioasyncdefformat_string(tag:str,count:int)->str:returnf'T-minus{count} ({tag})'asyncdefcountdown(tag:str,count:int)->str:whilecount>0:my_str=awaitformat_string(tag,count)# type is inferred to be strprint(my_str)awaitasyncio.sleep(0.1)count-=1return"Blastoff!"asyncio.run(countdown("Millennium Falcon",5))
The result of calling anasyncdef functionwithout awaiting willautomatically be inferred to be a value of typeCoroutine[Any,Any,T], which is a subtype ofAwaitable[T]:
my_coroutine=countdown("Millennium Falcon",5)reveal_type(my_coroutine)# Revealed type is "typing.Coroutine[Any, Any, builtins.str]"
If you have an asynchronous iterator, you can use theAsyncIterator type in your annotations:
fromcollections.abcimportAsyncIteratorfromtypingimportOptionalimportasyncioclassarange:def__init__(self,start:int,stop:int,step:int)->None:self.start=startself.stop=stopself.step=stepself.count=start-stepdef__aiter__(self)->AsyncIterator[int]:returnselfasyncdef__anext__(self)->int:self.count+=self.stepifself.count==self.stop:raiseStopAsyncIterationelse:returnself.countasyncdefrun_countdown(tag:str,countdown:AsyncIterator[int])->str:asyncforiincountdown:print(f'T-minus{i} ({tag})')awaitasyncio.sleep(0.1)return"Blastoff!"asyncio.run(run_countdown("Serenity",arange(5,0,-1)))
Async generators (introduced inPEP 525) are an easy way to createasync iterators:
fromcollections.abcimportAsyncGeneratorfromtypingimportOptionalimportasyncio# Could also type this as returning AsyncIterator[int]asyncdefarange(start:int,stop:int,step:int)->AsyncGenerator[int,None]:current=startwhile(step>0andcurrent<stop)or(step<0andcurrent>stop):yieldcurrentcurrent+=stepasyncio.run(run_countdown("Battlestar Galactica",arange(5,0,-1)))
One common confusion is that the presence of ayield statement in anasyncdef function has an effect on the type of the function:
fromcollections.abcimportAsyncIteratorasyncdefarange(stop:int)->AsyncIterator[int]:# When called, arange gives you an async iterator# Equivalent to Callable[[int], AsyncIterator[int]]i=0whilei<stop:yieldii+=1asyncdefcoroutine(stop:int)->AsyncIterator[int]:# When called, coroutine gives you something you can await to get an async iterator# Equivalent to Callable[[int], Coroutine[Any, Any, AsyncIterator[int]]]returnarange(stop)asyncdefmain()->None:reveal_type(arange(5))# Revealed type is "typing.AsyncIterator[builtins.int]"reveal_type(coroutine(5))# Revealed type is "typing.Coroutine[Any, Any, typing.AsyncIterator[builtins.int]]"awaitarange(5)# Error: Incompatible types in "await" (actual type "AsyncIterator[int]", expected type "Awaitable[Any]")reveal_type(awaitcoroutine(5))# Revealed type is "typing.AsyncIterator[builtins.int]"
This can sometimes come up when trying to define base classes, Protocols or overloads:
fromcollections.abcimportAsyncIteratorfromtypingimportProtocol,overloadclassLauncherIncorrect(Protocol):# Because launch does not have yield, this has type# Callable[[], Coroutine[Any, Any, AsyncIterator[int]]]# instead of# Callable[[], AsyncIterator[int]]asyncdeflaunch(self)->AsyncIterator[int]:raiseNotImplementedErrorclassLauncherCorrect(Protocol):deflaunch(self)->AsyncIterator[int]:raiseNotImplementedErrorclassLauncherAlsoCorrect(Protocol):asyncdeflaunch(self)->AsyncIterator[int]:raiseNotImplementedErrorifFalse:yield0# The type of the overloads is independent of the implementation.# In particular, their type is not affected by whether or not the# implementation contains a `yield`.# Use of `def`` makes it clear the type is Callable[..., AsyncIterator[int]],# whereas with `async def` it would be Callable[..., Coroutine[Any, Any, AsyncIterator[int]]]@overloaddeflaunch(*,count:int=...)->AsyncIterator[int]:...@overloaddeflaunch(*,time:float=...)->AsyncIterator[int]:...asyncdeflaunch(*,count:int=0,time:float=0)->AsyncIterator[int]:# The implementation of launch is an async generator and contains a yieldyield0