mypy β Static Type Checker#
What it is#
mypy checks Python type annotations statically β before you run the code. It catches mismatched types, missing return statements, None-dereferences, and incorrect function signatures without executing the program.
Install#
pip install mypy
# Stubs for popular packages (only needed for untyped libraries)
pip install types-requests
Quick example#
# greet.py
def greet(name: str) -> str:
return f"Hello, {name}"
result: str = greet("Alice")
print(result)
# β type error: greet expects str, not int
bad = greet(42)
mypy greet.py
Output:
greet.py:8: error: Argument 1 to "greet" has incompatible type "int"; expected "str" [arg-type]
Found 1 error in 1 file (checked 1 source file)
When / why to use it#
- Catch bugs in function signatures before tests surface them.
- Self-documenting code: type hints describe intent better than docstrings.
- IDE completion: editors use type info for better autocomplete and inline errors.
- Gradually adoptable: add to one module at a time without converting the whole project.
Common pitfalls#
[!WARNING] Untyped third-party packages β mypy reports
Missing library stubs or py.typed markerfor libraries without type annotations. Either installtypes-<pkg>stubs or addignore_missing_imports = truein your config.
[!WARNING]
Optionalis not the same as a union withNoneβ in Python 3.10+ you can writestr | Nonedirectly. Before 3.10 you needOptional[str]fromtyping. mypy accepts both.
[!TIP] Start with
--ignore-missing-importsso untyped third-party packages donβt block you. Tighten later with--strictonce youβre ready.
Gradual typing#
mypy is designed for incremental adoption. Start by annotating only the public API of critical modules:
# Step 1: check specific files
mypy my_module.py
# Step 2: check the whole package with lenient config
mypy src/ --ignore-missing-imports
# Step 3: strict mode (requires everything to be annotated)
mypy src/ --strict
Richer example β Optional, generics, and Protocol#
# typed_ops.py
from typing import Protocol, TypeVar
T = TypeVar("T")
class Comparable(Protocol):
def __lt__(self, other: "Comparable") -> bool: ...
def maximum(items: list[T], key=None) -> T:
if not items:
raise ValueError("empty list")
return max(items, key=key)
def find_first(items: list[T], predicate) -> T | None:
for item in items:
if predicate(item):
return item
return None
nums = [3, 1, 4, 1, 5, 9]
print(maximum(nums))
name = find_first(["Alice", "Bob", "Carol"], lambda s: s.startswith("B"))
print(name)
mypy typed_ops.py --strict
Output:
Success: no issues found in 1 source file
print(name.upper()) # β name could be None
typed_ops.py:22: error: Item "None" of "str | None" has no attribute "upper" [union-attr]
Common error codes#
| Code | Meaning |
|---|---|
[arg-type] | Argument type mismatch |
[return-value] | Wrong return type |
[union-attr] | Attribute access on a union that includes None |
[assignment] | Variable assigned the wrong type |
[no-untyped-def] | Function has no type annotations (strict mode) |
[import-untyped] | Imported module has no type stubs |
[override] | Overridden method is incompatible with base class |
Suppress a specific error#
result = some_untyped_function() # type: ignore[no-untyped-call]
Use sparingly β prefer fixing the root cause.
pyproject.toml configuration#
[tool.mypy]
python_version = "3.12"
strict = true
ignore_missing_imports = true
exclude = ["tests/", "migrations/"]
Checking an entire project#
mypy src/ --show-error-codes --pretty
Output:
src/api.py:14: error: Function is missing a return type annotation [no-untyped-def]
src/utils.py:33: error: Argument 1 has incompatible type "str | None"; expected "str" [arg-type]
Found 2 errors in 2 files (checked 18 source files)