First steps
Type system reference
Configuring and running mypy
Miscellaneous
Project Links
Idiomatic use of type annotations can sometimes run up against what a givenversion of Python considers legal code. This section describes these scenariosand explains how to get your code running again. Generally speaking, we havethree tools at our disposal:
Use of string literal types or type comments
Use oftyping.TYPE_CHECKING
Use offrom__future__importannotations
(PEP 563)
We provide a description of these before moving onto discussion of specificproblems you may encounter.
Mypy lets you add type annotations using the (now deprecated)#type:
type comment syntax. These were required with Python versions older than 3.6,since they didn’t support type annotations on variables. Example:
a=1# type: intdeff(x):# type: (int) -> intreturnx+1# Alternative type comment syntax for functions with many argumentsdefsend_email(address,# type: Union[str, List[str]]sender,# type: strcc,# type: Optional[List[str]]subject='',body=None# type: List[str]):# type: (...) -> bool
Type comments can’t cause runtime errors because comments are not evaluated byPython.
In a similar way, using string literal types sidesteps the problem ofannotations that would cause runtime errors.
Any type can be entered as a string literal, and you can combinestring-literal types with non-string-literal types freely:
deff(a:list['A'])->None:...# OK, prevents NameError since A is defined laterdefg(n:'int')->None:...# Also OK, though not usefulclassA:pass
String literal types are never needed in#type:
comments andstub files.
String literal types must be defined (or imported) laterin the same module.They cannot be used to leave cross-module references unresolved. (For dealingwith import cycles, seeImport cycles.)
Many of the issues described here are caused by Python trying to evaluateannotations. Future Python versions (potentially Python 3.14) will by default nolonger attempt to evaluate function and variable annotations. This behaviour ismade available in Python 3.7 and later through the use offrom__future__importannotations
.
This can be thought of as automatic string literal-ification of all function andvariable annotations. Note that function and variable annotations are stillrequired to be valid Python syntax. For more details, seePEP 563.
Note
Even with the__future__
import, there are some scenarios that couldstill require string literals or result in errors, typically involving useof forward references or generics in:
type aliases not defined using thetype
statement;
type definitions (seeTypeVar
,NewType
,NamedTuple
);
base classes.
# base class examplefrom__future__importannotationsclassA(tuple['B','C']):...# String literal types needed hereclassB:...classC:...
Warning
Some libraries may have use cases for dynamic evaluation of annotations, forinstance, through use oftyping.get_type_hints
oreval
. If yourannotation would raise an error when evaluated (say by usingPEP 604syntax with Python 3.9), you may need to be careful when using suchlibraries.
Thetyping
module defines aTYPE_CHECKING
constantthat isFalse
at runtime but treated asTrue
while type checking.
Since code insideifTYPE_CHECKING:
is not executed at runtime, it providesa convenient way to tell mypy something without the code being evaluated atruntime. This is most useful for resolvingimport cycles.
Python does not allow references to a class object before the class isdefined (aka forward reference). Thus this code does not work as expected:
deff(x:A)->None:...# NameError: name "A" is not definedclassA:...
Starting from Python 3.7, you can addfrom__future__importannotations
toresolve this, as discussed earlier:
from__future__importannotationsdeff(x:A)->None:...# OKclassA:...
For Python 3.6 and below, you can enter the type as a string literal or type comment:
deff(x:'A')->None:...# OK# Also OKdefg(x):# type: (A) -> None...classA:...
Of course, instead of using future annotations import or string literal types,you could move the function definition after the class definition. This is notalways desirable or even possible, though.
An import cycle occurs where module A imports module B and module Bimports module A (perhaps indirectly, e.g.A->B->C->A
).Sometimes in order to add type annotations you have to add extraimports to a module and those imports cause cycles that didn’t existbefore. This can lead to errors at runtime like:
ImportError: cannot import name 'b' from partially initialized module 'A' (most likely due to a circular import)
If those cycles do become a problem when running your program, there’s a trick:if the import is only needed for type annotations and you’re using a) thefuture annotations import, or b) string literals or typecomments for the relevant annotations, you can write the imports insideifTYPE_CHECKING:
so that they are not executed at runtime. Example:
Filefoo.py
:
fromtypingimportTYPE_CHECKINGifTYPE_CHECKING:importbardeflistify(arg:'bar.BarClass')->'list[bar.BarClass]':return[arg]
Filebar.py
:
fromfooimportlistifyclassBarClass:deflistifyme(self)->'list[BarClass]':returnlistify(self)
Some classes are declared asgeneric in stubs, but notat runtime.
In Python 3.8 and earlier, there are several examples within the standard library,for instance,os.PathLike
andqueue.Queue
. Subscriptingsuch a class will result in a runtime error:
fromqueueimportQueueclassTasks(Queue[str]):# TypeError: 'type' object is not subscriptable...results:Queue[int]=Queue()# TypeError: 'type' object is not subscriptable
To avoid errors from use of these generics in annotations, just use thefuture annotations import (or string literals or typecomments for Python 3.6 and below).
To avoid errors when inheriting from these classes, things are a little morecomplicated and you need to usetyping.TYPE_CHECKING:
fromtypingimportTYPE_CHECKINGfromqueueimportQueueifTYPE_CHECKING:BaseQueue=Queue[str]# this is only processed by mypyelse:BaseQueue=Queue# this is not seen by mypy but will be executed at runtimeclassTasks(BaseQueue):# OK...task_queue:Tasksreveal_type(task_queue.get())# Reveals str
If your subclass is also generic, you can use the following (using thelegacy syntax for generic classes):
fromtypingimportTYPE_CHECKING,TypeVar,GenericfromqueueimportQueue_T=TypeVar("_T")ifTYPE_CHECKING:class_MyQueueBase(Queue[_T]):passelse:class_MyQueueBase(Generic[_T],Queue):passclassMyQueue(_MyQueueBase[_T]):passtask_queue:MyQueue[str]reveal_type(task_queue.get())# Reveals str
In Python 3.9 and later, we can just inherit directly fromQueue[str]
orQueue[T]
since itsqueue.Queue
implements__class_getitem__()
, sothe class object can be subscripted at runtime. You may still encounter issues (even ifyou use a recent Python version) when subclassing generic classes defined in third-partylibraries if types are generic only in stubs.
Sometimes stubs that you’re using may define types you wish to reuse that donot exist at runtime. Importing these types naively will cause your code to failat runtime withImportError
orModuleNotFoundError
. Similar to previoussections, these can be dealt with by usingtyping.TYPE_CHECKING:
from__future__importannotationsfromtypingimportTYPE_CHECKINGifTYPE_CHECKING:from_typeshedimportSupportsRichComparisondeff(x:SupportsRichComparison)->None
Thefrom__future__importannotations
is required to avoidaNameError
when using the imported symbol.For more information and caveats, see the section onfuture annotations.
Starting with Python 3.9 (PEP 585), the type objects of many collections inthe standard library support subscription at runtime. This means that you nolonger have to import the equivalents fromtyping
; you can simply usethe built-in collections or those fromcollections.abc
:
fromcollections.abcimportSequencex:list[str]y:dict[int,str]z:Sequence[str]=x
There is limited support for using this syntax in Python 3.7 and later as well:if you usefrom__future__importannotations
, mypy will understand thissyntax in annotations. However, since this will not be supported by the Pythoninterpreter at runtime, make sure you’re aware of the caveats mentioned in thenotes atfuture annotations import.
Starting with Python 3.10 (PEP 604), you can spell union types asx:int|str
, instead ofx:typing.Union[int,str]
.
There is limited support for using this syntax in Python 3.7 and later as well:if you usefrom__future__importannotations
, mypy will understand thissyntax in annotations, string literal types, type comments and stub files.However, since this will not be supported by the Python interpreter at runtime(if evaluated,int|str
will raiseTypeError:unsupportedoperandtype(s)for|:'type'and'type'
), make sure you’re aware of the caveats mentioned inthe notes atfuture annotations import.
You may find yourself wanting to use features added to thetyping
module in earlier versions of Python than the addition.
The easiest way to do this is to install and use thetyping_extensions
package from PyPI for the relevant imports, for example:
fromtyping_extensionsimportTypeIs
If you don’t want to rely ontyping_extensions
being installed on newerPythons, you could alternatively use:
importsysifsys.version_info>=(3,13):fromtypingimportTypeIselse:fromtyping_extensionsimportTypeIs
This plays nicely well with followingPEP 508 dependency specification:typing_extensions;python_version<"3.13"