First steps
Type system reference
Configuring and running mypy
Miscellaneous
Project Links
This chapter introduces some core concepts of mypy, including functionannotations, thetyping module, stub files, and more.
If you’re looking for a quick intro, see themypy cheatsheet.
If you’re unfamiliar with the concepts of static and dynamic type checking,be sure to read this chapter carefully, as the rest of the documentationmay not make much sense otherwise.
Mypy requires Python 3.9 or later to run. You can install mypy using pip:
$python3-mpipinstallmypy
Once mypy is installed, run it by using themypy tool:
$mypyprogram.py
This command makes mypytype check yourprogram.py file and printout any errors it finds. Mypy will type check your codestatically: thismeans that it will check for errors without ever running your code, justlike a linter.
This also means that you are always free to ignore the errors mypy reports,if you so wish. You can always use the Python interpreter to run your code,even if mypy reports errors.
However, if you try directly running mypy on your existing Python code, itwill most likely report little to no errors. This is a feature! It makes iteasy to adopt mypy incrementally.
In order to get useful diagnostics from mypy, you must addtype annotationsto your code. See the section below for details.
A function without type annotations is considered to bedynamically typed by mypy:
defgreeting(name):return'Hello '+name
By default, mypy willnot type check dynamically typed functions. This meansthat with a few exceptions, mypy will not report any errors with regular unannotated Python.
This is the case even if you misuse the function!
defgreeting(name):return'Hello '+name# These calls will fail when the program runs, but mypy does not report an error# because "greeting" does not have type annotations.greeting(123)greeting(b"Alice")
We can get mypy to detect these kinds of bugs by addingtype annotations (alsoknown astype hints). For example, you can tell mypy thatgreeting both acceptsand returns a string like so:
# The "name: str" annotation says that the "name" argument should be a string# The "-> str" annotation says that "greeting" will return a stringdefgreeting(name:str)->str:return'Hello '+name
This function is nowstatically typed: mypy will use the provided type hintsto detect incorrect use of thegreeting function and incorrect use ofvariables within thegreeting function. For example:
defgreeting(name:str)->str:return'Hello '+namegreeting(3)# Argument 1 to "greeting" has incompatible type "int"; expected "str"greeting(b'Alice')# Argument 1 to "greeting" has incompatible type "bytes"; expected "str"greeting("World!")# No errordefbad_greeting(name:str)->str:return'Hello '*name# Unsupported operand types for * ("str" and "str")
Being able to pick whether you want a function to be dynamically or staticallytyped can be very helpful. For example, if you are migrating an existingPython codebase to use static types, it’s usually easier to migrate by incrementallyadding type hints to your code rather than adding them all at once. Similarly,when you are prototyping a new feature, it may be convenient to initially implementthe code using dynamic typing and only add type hints later once the code is more stable.
Once you are finished migrating or prototyping your code, you can make mypy warn youif you add a dynamic function by mistake by using the--disallow-untyped-defsflag. You can also get mypy to provide some limited checking of dynamically typedfunctions by using the--check-untyped-defs flag.SeeThe mypy command line for more information on configuring mypy.
Mypy has astrict mode that enables a number of additional checks,like--disallow-untyped-defs.
If you run mypy with the--strict flag, youwill basically never get a type related error at runtime without a correspondingmypy error, unless you explicitly circumvent mypy somehow.
However, this flag will probably be too aggressive if you are tryingto add static types to a large, existing codebase. SeeUsing mypy with an existing codebasefor suggestions on how to handle that case.
Mypy is very configurable, so you can start with using--strictand toggle off individual checks. For instance, if you use many thirdparty libraries that do not have types,--ignore-missing-importsmay be useful. SeeIntroduce stricter options for how to build up to--strict.
SeeThe mypy command line andThe mypy configuration file for a complete reference onconfiguration options.
So far, we’ve added type hints that use only basic concrete types likestr andfloat. What if we want to express more complex types,such as “a list of strings” or “an iterable of ints”?
For example, to indicate that some function can accept a list ofstrings, use thelist[str] type (Python 3.9 and later):
defgreet_all(names:list[str])->None:fornameinnames:print('Hello '+name)names=["Alice","Bob","Charlie"]ages=[10,20,30]greet_all(names)# Ok!greet_all(ages)# Error due to incompatible types
Thelist type is an example of something called ageneric type: it canaccept one or moretype parameters. In this case, weparameterizedlistby writinglist[str]. This lets mypy know thatgreet_all accepts specificallylists containing strings, and not lists containing ints or any other type.
In the above examples, the type signature is perhaps a little too rigid.After all, there’s no reason why this function must acceptspecifically a list –it would run just fine if you were to pass in a tuple, a set, or any other custom iterable.
You can express this idea usingcollections.abc.Iterable:
fromcollections.abcimportIterable# or "from typing import Iterable"defgreet_all(names:Iterable[str])->None:fornameinnames:print('Hello '+name)
This behavior is actually a fundamental aspect of the PEP 484 type system: whenwe annotate some variable with a typeT, we are actually telling mypy thatvariable can be assigned an instance ofT, or an instance of asubtype ofT.That is,list[str] is a subtype ofIterable[str].
This also applies to inheritance, so if you have a classChild that inherits fromParent, then a value of typeChild can be assigned to a variable of typeParent.For example, aRuntimeError instance can be passed to a function that is annotatedas taking anException.
As another example, suppose you want to write a function that can accepteitherints or strings, but no other types. You can express this using aunion type. For example,int is a subtype ofint|str:
defnormalize_id(user_id:int|str)->str:ifisinstance(user_id,int):returnf'user-{100_000+user_id}'else:returnuser_id
Note
If using Python 3.9 or earlier, usetyping.Union[int,str] instead ofint|str, or usefrom__future__importannotations at the top ofthe file (seeAnnotation issues at runtime).
Thetyping module contains many other useful types.
For a quick overview, look through themypy cheatsheet.
For a detailed overview (including information on how to make your owngeneric types or your own type aliases), look through thetype system reference.
Note
When adding types, the convention is to import typesusing the formfromtypingimport<name> (as opposed to doingjustimporttyping orimporttypingast orfromtypingimport*).
For brevity, we often omit imports fromtyping orcollections.abcin code examples, but mypy will give an error if you use types such asIterable without first importing them.
Note
In some examples we use capitalized variants of types, such asList, and sometimes we use plainlist. They are equivalent,but the prior variant is needed if you are using Python 3.8 or earlier.
Once you have added type hints to a function (i.e. made it statically typed),mypy will automatically type check that function’s body. While doing so,mypy will try andinfer as many details as possible.
We saw an example of this in thenormalize_id function above – mypy understandsbasicisinstance checks and so can infer that theuser_id variable was oftypeint in the if-branch and of typestr in the else-branch.
As another example, consider the following function. Mypy can type check this functionwithout a problem: it will use the available context and deduce thatoutput must beof typelist[float] and thatnum must be of typefloat:
defnums_below(numbers:Iterable[float],limit:float)->list[float]:output=[]fornuminnumbers:ifnum<limit:output.append(num)returnoutput
For more details, seeType inference and type annotations.
Mypy can also understand how to work with types from libraries that you use.
For instance, mypy comes out of the box with an intimate knowledge of thePython standard library. For example, here is a function which uses thePath object from thepathlib standard library module:
frompathlibimportPathdefload_template(template_path:Path,name:str)->str:# Mypy knows that `template_path` has a `read_text` method that returns a strtemplate=template_path.read_text()# ...so it understands this line type checksreturntemplate.replace('USERNAME',name)
If a third party library you usedeclares support for type checking,mypy will type check your use of that library based on the type hintsit contains.
However, if the third party library does not have type hints, mypy willcomplain about missing type information.
prog.py:1: error: Library stubs not installed for "yaml"prog.py:1: note: Hint: "python3 -m pip install types-PyYAML"prog.py:2: error: Library stubs not installed for "requests"prog.py:2: note: Hint: "python3 -m pip install types-requests"...
In this case, you can provide mypy a different source of type information,by installing astub package. A stub package is a package that containstype hints for another library, but no actual code.
$python3-mpipinstalltypes-PyYAMLtypes-requests
Stubs packages for a distribution are often namedtypes-<distribution>.Note that a distribution name may be different from the name of the package thatyou import. For example,types-PyYAML contains stubs for theyamlpackage.
For more discussion on strategies for handling errors about libraries withouttype information, refer toMissing imports.
For more information about stubs, seeStub files.
If you are in a hurry and don’t want to read lots of documentationbefore getting started, here are some pointers to quick learningresources:
Read themypy cheatsheet.
ReadUsing mypy with an existing codebase if you have a significant existingcodebase without many type annotations.
Read theblog postabout the Zulip project’s experiences with adopting mypy.
If you prefer watching talks instead of reading, here aresome ideas:
Carl Meyer:Type Checked Python in the Real World(PyCon 2018)
Greg Price:Clearer Code at Scale: Static Types at Zulip and Dropbox(PyCon 2018)
Look atsolutions to common issues with mypy ifyou encounter problems.
You can ask questions about mypy in themypy issue tracker andtypingGitter chat.
For general questions about Python typing, try posting attyping discussions.
You can also continue reading this document and skip sections thataren’t relevant for you. You don’t need to read sections in order.