Python Type Hints in Code Review: MyPy Integration and Best Practices

Python's type hint system, introduced in Python 3.5, has revolutionized how we write and review Python code. Combined with MyPy's static type checking, type hints catch bugs before they reach production and make code reviews more effective. This guide shows you how to leverage type hints in your code review process for maximum impact.
Key Benefits of Type Hints in Code Review
- •Catch 15% more bugs: Static analysis finds errors that manual review misses
- •Reduce review time: Reviewers focus on logic instead of guessing parameter types
- •Improve documentation: Code becomes self-documenting with clear interfaces
- •Enable better tooling: IDEs provide superior autocomplete and error detection
Why Type Hints Matter in Code Review
Traditional Python code review often involves reviewers mentally tracking variable types, guessing function parameters, and trying to understand complex data flows. Type hints eliminate this cognitive overhead, allowing reviewers to focus on business logic, edge cases, and architectural decisions.
Before and After: Code Review Impact
Without Type Hints
- • "What type does this function return?"
- • "Can this parameter be None?"
- • "What's the structure of this dict?"
- • "Does this work with strings or bytes?"
- • Manual testing required to verify types
With Type Hints
- • Clear function signatures document behavior
- • Optional types explicitly handled
- • Complex data structures well-defined
- • String/bytes distinction clear
- • MyPy catches type errors automatically
Essential Type Hint Patterns for Code Review
1. Function Signatures
Always include type hints for function parameters and return values. This is the foundation of effective type-checked code:
def process_user_data(data, include_inactive=False):
if include_inactive:
return [user for user in data if user.get('status')]
return [user for user in data if user.get('status') == 'active']
✅ Clear and reviewable:from typing import List, Dict, Any
def process_user_data(
data: List[Dict[str, Any]],
include_inactive: bool = False
) -> List[Dict[str, Any]]:
if include_inactive:
return [user for user in data if user.get('status')]
return [user for user in data if user.get('status') == 'active']
2. Optional Types and None Handling
Make None-able values explicit to prevent AttributeError bugs:
def get_user_email(user_id):
user = database.get_user(user_id)
return user.email # What if user is None?
✅ Explicit None handling:from typing import Optional
def get_user_email(user_id: int) -> Optional[str]:
user: Optional[User] = database.get_user(user_id)
if user is None:
return None
return user.email
3. Complex Data Structures
Use TypedDict, dataclasses, or Pydantic models for complex data structures instead of generic dictionaries:
def calculate_order_total(order):
return order['items_total'] + order['tax'] + order.get('shipping', 0)
✅ Well-defined structure:from typing import TypedDict
from decimal import Decimal
class OrderData(TypedDict):
items_total: Decimal
tax: Decimal
shipping: Decimal
def calculate_order_total(order: OrderData) -> Decimal:
return order['items_total'] + order['tax'] + order['shipping']
MyPy Configuration for Code Review
Essential mypy.ini Settings
Configure MyPy to catch the most common issues without being overly strict for legacy code:
[mypy]
python_version = 3.9
warn_return_any = True
warn_unused_configs = True
disallow_untyped_defs = True
disallow_incomplete_defs = True
check_untyped_defs = True
disallow_untyped_decorators = True
warn_redundant_casts = True
warn_unused_ignores = True
warn_no_return = True
warn_unreachable = True
strict_equality = True
# Start strict, relax for legacy code
[mypy-legacy.*]
disallow_untyped_defs = False
# Third-party libraries without stubs
[mypy-some_third_party_lib.*]
ignore_missing_imports = True
Gradual Adoption Strategy
Implement type checking incrementally to avoid overwhelming your team:
4-Phase Adoption Plan
Code Review Checklist for Python Type Hints
Function-Level Checks
Module-Level Checks
Advanced Type Hint Patterns
1. Protocol for Duck Typing
Use Protocol to define interfaces for dependency injection and testing:
from typing import Protocol
class Emailer(Protocol):
def send_email(self, to: str, subject: str, body: str) -> bool:
...
class UserService:
def __init__(self, emailer: Emailer) -> None:
self.emailer = emailer
def notify_user(self, user_id: str, message: str) -> bool:
# Works with any object that implements send_email
return self.emailer.send_email(
to=f"{user_id}@example.com",
subject="Notification",
body=message
)
2. Generic Types for Reusability
Create reusable, type-safe components with generics:
from typing import TypeVar, Generic, List, Optional
T = TypeVar('T')
class Repository(Generic[T]):
def __init__(self) -> None:
self._items: List[T] = []
def add(self, item: T) -> None:
self._items.append(item)
def get_by_id(self, id: int) -> Optional[T]:
if 0 <= id < len(self._items):
return self._items[id]
return None
# Usage with specific types
user_repo = Repository[User]() # Type checker knows this stores Users
order_repo = Repository[Order]() # This stores Orders
3. Literal Types for Constants
Use Literal types to restrict values to specific constants:
from typing import Literal
Status = Literal['pending', 'approved', 'rejected']
LogLevel = Literal['DEBUG', 'INFO', 'WARNING', 'ERROR']
def update_order_status(order_id: str, status: Status) -> bool:
# MyPy will error if status isn't one of the literal values
return database.update_order(order_id, status)
def log_message(message: str, level: LogLevel = 'INFO') -> None:
# Type checker ensures only valid log levels are used
logger.log(level, message)
Common Type Hint Mistakes in Code Review
1. Using Any Too Liberally
❌ Avoid Any without justification
from typing import Any
def process_data(data: Any) -> Any: # Defeats the purpose of type hints
return data.do_something()
✅ Be specific about types
from typing import Union, Dict, List
def process_data(data: Union[Dict[str, str], List[str]]) -> str:
if isinstance(data, dict):
return ','.join(data.values())
return ','.join(data)
2. Forgetting to Handle None
❌ Missing None checks
def get_user_name(user: Optional[User]) -> str:
return user.name # Potential AttributeError if user is None
✅ Explicit None handling
def get_user_name(user: Optional[User]) -> str:
if user is None:
return "Anonymous"
return user.name
3. Incorrect Generic Usage
❌ Bare containers without element types
def process_items(items: list) -> dict: # What kind of list? What dict structure?
return {'{item.id: item for item in items}'}
✅ Specific generic types
from typing import List, Dict
def process_items(items: List[Item]) -> Dict[str, Item]:
return {'{item.id: item for item in items}'}
Integrating MyPy into Your Review Process
1. Pre-commit Hooks
Automatically run MyPy before commits to catch type errors early:
repos:
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.991
hooks:
- id: mypy
additional_dependencies: [types-requests, types-PyYAML]
exclude: ^(docs/|scripts/)
2. CI/CD Integration
Add MyPy checks to your continuous integration pipeline:
name: Type Check
on: [push, pull_request]
jobs:
type-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- run: pip install mypy types-requests
- run: mypy src/
- run: mypy --strict new_modules/
3. IDE Integration
Configure your team's IDEs to show MyPy errors in real-time. Popular options include:
- VS Code: Python extension with MyPy linting enabled
- PyCharm: Built-in type checking with configurable strictness
- Vim/Neovim: ALE or coc-pyright plugins
- Sublime Text: LSP-pyright plugin
Measuring Type Hint Adoption
Coverage Metrics
Track type hint adoption across your codebase:
MyPy Coverage Report
# Generate coverage report
mypy --html-report mypy-report/ src/
# Check specific coverage percentage
mypy --linecount-report linecount-report/ src/
Quality Metrics
- Type coverage percentage: Aim for 80%+ on new code
- MyPy error count: Track reduction over time
- # type: ignore usage: Should decrease as code improves
- Runtime type errors: AttributeError, TypeError should decrease
Frequently Asked Questions
Should we type hint internal/private functions?
Yes, type hints help with maintainability and debugging even for internal functions. The only exception might be very short, obvious utility functions.
How do we handle third-party libraries without type stubs?
Use `# type: ignore` for import lines, or create your own stub files for frequently used libraries. Check if stubs exist in the typeshed project first.
What about performance impact of type hints?
Type hints have zero runtime performance impact in Python. They're stored in `__annotations__` and ignored during execution unless explicitly accessed.
Can we use type hints with older Python versions?
Yes! Use `from __future__ import annotations` for Python 3.7+ or string annotations for older versions. MyPy supports Python 3.6+.
Advanced MyPy Features
1. Stub Files for Gradual Typing
Create .pyi stub files to add types to existing code without modifying the source:
from typing import List, Dict, Any
def process_data(data: List[Dict[str, Any]]) -> bool: ...
def get_config() -> Dict[str, str]: ...
class LegacyProcessor:
def __init__(self, config: Dict[str, Any]) -> None: ...
def run(self) -> List[str]: ...
2. Plugin System
Use MyPy plugins for framework-specific type checking:
- Django: django-stubs for model and form type checking
- SQLAlchemy: sqlalchemy2-stubs for ORM type safety
- Pydantic: Built-in MyPy plugin for model validation
3. Custom Type Checkers
Write custom MyPy plugins for domain-specific type checking needs, such as validating business rules or API contracts.
Supercharge Your Python Code Reviews
Propel's AI understands Python type hints and can automatically catch type-related issues in your code reviews. Get intelligent feedback that complements MyPy's static analysis.
Conclusion
Type hints and MyPy transform Python from a dynamically typed language into one with optional static typing—giving you the best of both worlds. By incorporating type checking into your code review process, you'll catch more bugs, improve code documentation, and make your codebase more maintainable.
Start with new code, gradually expand coverage, and watch as your code reviews become more focused on business logic rather than basic type safety. Your future self (and your teammates) will thank you.
Quick Start Checklist
- ✓ Install MyPy:
pip install mypy
- ✓ Add type hints to your next function
- ✓ Run
mypy your_file.py
- ✓ Configure mypy.ini with basic settings
- ✓ Add MyPy to your CI pipeline
Enhance Your Python Code Reviews
See how Propel's AI-powered reviews help catch Python type errors and enforce best practices automatically.