Mypy is a powerful, fast, and feature-rich static type checker designed to work with Python. It helps enforce code quality, catch type-related errors early, and boost productivity by analyzing your code’s type hints.
This guide will show you how to configure and use Mypy in your Python projects.
Prerequisites
Before you start with Mypy, make sure you have:
- A recent version of Python installed (Python 3.13 or higher is recommended).
- Basic knowledge of Python type annotations.
Step 1 — Setting up the project directory
In this section, you will create a new project directory, set up a virtual environment, and install Mypy within that environment.
First, create a new directory for your project and navigate into it:
Next, create and activate a virtual environment so that any packages you install don’t affect other Python projects on your system:
Once your virtual environment is active, install Mypy using pip, the standard Python package manager:
This will install Mypy as a Python package within the virtual environment, making it available for static type checking in your project. You can confirm that Mypy is installed correctly by running:
This command will print the installed version number if everything is set up correctly, indicating that Mypy is ready to analyze your Python code. Here is an example of what you might see:
Now that Mypy is installed, you can continue configuring it for your project and start taking advantage of its static type-checking capabilities.
Step 2 — Getting started with Mypy
Now that Mypy is installed, you can see how it checks Python code. In this step, you will create a simple Python script, introduce a type mismatch, and use Mypy to detect the error.
Begin by creating a directory named src in your project and then add a file called main.py:
Here, greet is a function that expects a str parameter. Passing an integer (42) violates the type hint, which Mypy will flag as a mistake.
To run Mypy on the src folder, execute the following command from your project’s root directory:
You should see an error that indicates a type conflict, for example:
Here, Mypy catches the mismatch between str and int.
In the next step, you’ll learn how to configure Mypy more precisely to suit your project’s needs.
Step 3 — Configuring Mypy
In this step, you'll learn how to customize Mypy's behavior using a configuration file. you'll start with the most essential settings that provide a good balance between type safety and practicality.
Begin by creating a file named mypy.ini in your project’s root directory. This file lets you customize how Mypy checks your code.
Add the following code to the file:
The python_version indicates which Python version to use, disallow_untyped_defs requires function annotations, check_untyped_defs checks unannotated function bodies, and strict_optional handles Optional and None carefully.
Next, update the main.py file with the following code to see these checks in action:
When you run Mypy with:
You’ll see an error that highlights untyped_function is missing an annotation:
To fix this error, add type annotations to untyped_function:
Run Mypy again:
This time, you should see a success message with no errors:
With this change, your code passes Mypy's checks. As your project grows, feel free to explore additional settings and strictness levels in your mypy.ini to enforce even tighter type safety.
Step 4 — Using advanced type hints with Mypy
After configuring Mypy for basic checks, you can deepen your code’s safety by using Union and Optional from the typing module. These annotations help you manage variables that can take multiple types or might be None, ensuring Mypy flags potential type errors upfront.
Handling multiple types with Union
Sometimes, a function parameter can accept two or more different types. By annotating it with Union, you inform Mypy that multiple types are valid and prompt it to check each case correctly.
Update your main.py to include a function that takes either an integer or a string:
Running Mypy will show an error:
To fix this, distinguish between integer and string cases:
Handling None with Optional
A function or variable may sometimes yield a valid type or None. Using Optional clarifies that None is possible and warns you if you treat a None as a valid object.
Add a function returning Optional[str] to illustrate:
Running Mypy again highlights the issue:
To resolve this, check for None before calling upper() at the end of the file:
When you run Mypy now, you should see:
Explicitly handling each type scenario—whether multiple types through Union or the possibility of None through Optional—allows Mypy to catch subtle bugs.
This approach keeps your code clear, robust, and safer for future maintenance.
Step 5 — Using type guards and type narrowing
In the previous steps, you focused on how Mypy checks basic type hints and handles unions. In this step, you’ll refine those checks by leveraging type guards and type narrowing.
These features help Mypy understand exactly when your code transitions from one type to another, ensuring more accurate error detection.
Begin by updating your main.py to explore the difference between calling a method on a union type outright and safely narrowing its type with isinstance:
When you run Mypy you will see an error message like this:
Mypy detects that data might be an integer, which lacks the upper() method. To resolve this, you need to narrow the type at runtime using isinstance:
Run Mypy again:
You should now see this:
By using isinstance, you guide Mypy’s type inference and ensure that operations are performed on the correct types. This same principle applies when working with collections containing multiple types.
Using a custom type guard
For more detailed checks, especially when you need to confirm something more complex than a basic isinstance, Python 3.10+ allows you to write type guard functions.
A type guard function returns True only if its input is guaranteed to be a particular subtype, and you specify that subtype using TypeGuard.
In the following example, is_alpha_string checks if data is a string containing only alphabetic characters. When is_alpha_string(data) returns True, Mypy treats data as a fully alphabetic string:
When you run:
Mypy concludes that within the if is_alpha_string(data): block, data must be an alphabetic string. You don’t need any extra checks, and any attempts to misuse data in that block will be flagged before runtime.
Type guards and type narrowing allow Mypy to track your code’s logic with greater precision. You reduce the risk of calling invalid methods on your data by specifying exactly when a union type transitions to a narrower subtype.
As your codebase expands, these features become vital to maintaining clarity and catching subtle bugs early.
Step 6 — Using generic types with Mypy
After learning about type guards and narrowing, the next step is understanding generic types.
Generics allow you to write flexible code that works with different types while maintaining type safety. They're especially useful when writing classes and functions that should work with various data types.
Begin by updating your main.py with a basic example of a container class without proper generic typing:
Running Mypy shows errors because our functions lack type annotations:
Python itself won’t break if you pass any type to this Container, you won’t see a runtime error here. However, Mypy cannot verify type safety because it cannot know which type each container should hold.
To fix this, you need to declare the class as generic:
Here, you introduce a TypeVar named T and make Container inherit from Generic[T]. Mypy now knows that each container instance holds a consistent type—either str, int, or another type you specify.
With this change, Mypy can enforce that string_container is strictly a Container[str] and integer_container is strictly a Container[int].
Running Mypy now shows no errors:
The generic version fixes our typing issues by using TypeVar as a placeholder type and Generic[T] to make our class work with any type. By annotating our methods with T, Mypy can track the specific type used in each container instance, ensuring type-safe operations throughout your code.
Step 7 — Checking type coverage with Mypy
After exploring advanced type hints and generics, you might wonder how thoroughly your project is annotated. Mypy includes a coverage reporting feature that helps identify which parts of your code are fully typed and which need attention.
This is particularly valuable for larger projects where you want to gradually improve type coverage.
Begin by updating your main.py with a mix of typed and untyped functions:
Here, typed_greet has complete type annotations, while partially_typed_function is missing its parameter type. Running Mypy shows:
You can generate an HTML report to get a more detailed view of type coverage. First, install the required dependency:
Then, run Mypy with the --html-report flag:
This generates a mypy_coverage directory containing an HTML report. The index.html file provides an overview of type coverage across all modules, and Mypy links to it in the output:
Opening this file in a browser displays a breakdown of type annotations and any missing hints:
To resolve the issue, update partially_typed_function by adding the missing parameter type annotation:
Generate a new coverage report:
With all functions fully annotated, Mypy confirms there are no issues:
Opening the updated report in the browser reflects the improved type coverage:
Regularly checking type coverage ensures consistency across your codebase, making it easier to catch type-related issues early and maintain a high level of type safety.
Final Thoughts
This guide has shown how Mypy enhances code clarity, prevents runtime errors, and enforces consistency in your projects.
There’s still much more to explore. The official Mypy documentation explores advanced topics like custom type stubs, strict mode settings, and handling dynamic code.
Thanks for reading!