| name | mypy |
| description | [Applies to: **/*.py] This guide provides definitive, actionable best practices for using mypy to ensure robust static type checking in Python projects, focusing on modern patterns and common pitfalls. |
| source | cursor_mdc |
mypy Best Practices
mypy is the definitive static type checker for Python. Adhering to these guidelines ensures your codebase is robust, maintainable, and catches type errors before runtime.
1. Configuration & Integration
Always use a shared pyproject.toml for mypy configuration. This ensures consistency across the team and CI/CD. Pin your mypy version.
1.1. Centralized Configuration
Always configure mypy via pyproject.toml. This is the modern standard.
❌ BAD: mypy.ini or command-line flags only
✅ GOOD: pyproject.toml
[tool.mypy]
python_version = "3.11"
warn_unused_ignores = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
no_implicit_optional = true
strict_optional = true
warn_redundant_casts = true
warn_return_any = true
warn_unreachable = true
1.2. CI/CD & Pre-commit Hooks
Integrate mypy into your CI/CD pipeline and as a pre-commit hook. Catch errors early.
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: check-added-large-files
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
hooks:
- id: mypy
args: [--no-strict-optional, --ignore-missing-imports]
1.3. Incremental Adoption
For existing codebases, start with a minimal configuration and gradually enable stricter checks. Prioritize annotating widely imported modules.
[tool.mypy]
python_version = "3.11"
ignore_missing_imports = true
allow_untyped_defs = true
Then, enable stricter checks module by module or globally.
2. Type Hinting Best Practices
2.1. Prefer object over Any
Use object when a function truly accepts any value, especially for arguments. Any disables type checking.
❌ BAD:
def process_data(data: Any) -> None:
print(str(data))
✅ GOOD:
def process_data(data: object) -> None:
print(str(data))
2.2. Use TypeAlias for Type Aliases
Explicitly declare type aliases with TypeAlias for clarity.
❌ BAD:
Vector = list[float]
✅ GOOD:
from typing import TypeAlias
Vector: TypeAlias = list[float]
2.3. Concrete vs. Abstract Types
For arguments, prefer abstract types (protocols, ABCs) for flexibility. For return values, prefer concrete types for clear implementation.
❌ BAD (arguments too specific, returns too abstract):
from typing import MutableMapping
def map_data(input: list[str]) -> MutableMapping[str, int]:
return {s: len(s) for s in input}
✅ GOOD:
from collections.abc import Iterable
def map_data(input: Iterable[str]) -> dict[str, int]:
"""Maps strings to their lengths."""
return {s: len(s) for s in input}
2.4. Shorthand Union Syntax
Use | for unions and None as the last element.
❌ BAD:
from typing import Union, Optional
def get_item(key: Union[str, int]) -> Optional[str]:
...
✅ GOOD:
def get_item(key: str | int) -> str | None:
...
2.5. Built-in Generics
Use built-in generic types (list, dict, set) instead of typing aliases.
❌ BAD:
from typing import List, Dict
def process_items(items: List[str]) -> Dict[str, int]:
...
✅ GOOD:
def process_items(items: list[str]) -> dict[str, int]:
...
2.6. Protocols for Structural Typing
Define interfaces with Protocol for robust duck typing.
from typing import Protocol
class SupportsQuack(Protocol):
def quack(self) -> str:
...
def make_it_quack(duck: SupportsQuack) -> None:
print(duck.quack())
class MyDuck:
def quack(self) -> str:
return "Quack!"
make_it_quack(MyDuck())
3. Managing Type Errors
3.1. Specific # type: ignore
Only use # type: ignore as a last resort, always with a specific error code, and review regularly.
❌ BAD:
result = some_untyped_function()
✅ GOOD:
result = some_untyped_function()
3.2. Strict Mode for New Code
Enable strict = true in pyproject.toml for all new modules or the entire codebase once mature.
[tool.mypy]
strict = true
4. Performance Considerations
Use the mypy daemon (dmypy) for faster incremental checks in large codebases.
dmypy start
dmypy run -- foo.py bar.py
dmypy stop
5. Third-Party Libraries
For libraries without type stubs, install community stubs or generate your own.
mypy --install-types