skip to content

mypy β€” Static Type Checker

Catch type errors before runtime with mypy. Covers strict mode, common error codes, type: ignore annotations, gradual typing, and pyproject.toml configuration.

3 min read 14 snippets yesterday intermediate

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 marker for libraries without type annotations. Either install types-<pkg> stubs or add ignore_missing_imports = true in your config.

[!WARNING] Optional is not the same as a union with None β€” in Python 3.10+ you can write str | None directly. Before 3.10 you need Optional[str] from typing. mypy accepts both.

[!TIP] Start with --ignore-missing-imports so untyped third-party packages don’t block you. Tighten later with --strict once 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#

CodeMeaning
[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)