Type Hinting & Type Checking with Mypy and Pyright in Python
Python is a dynamically typed language, meaning you don't explicitly declare variable types. While this offers flexibility, it can lead to runtime errors, especially in larger codebases or when working in teams. Type hinting and static type checking are powerful tools that bring some of the benefits of static typing to Python, helping you write more robust, maintainable, and understandable code.
This tutorial will guide you through:
What Type Hinting is and Why it's Important.
How to use
mypy
for static type checking.How to use
pyright
for static type checking.
👉 What is Type Hinting and Why Use It?
Type Hinting involves adding annotations to your code to indicate the expected types of variables, function arguments, and return values. These hints are not enforced by the Python interpreter at runtime (Python remains dynamically typed), but they are crucial for static analysis tools.
Why is it important?
Early Error Detection: Type checkers (like
mypy
andpyright
) can analyze your code before it runs, catching potential type-related bugs that would otherwise only appear at runtime.Improved Readability and Maintainability: Type hints act as documentation, making it clearer what type of data a function expects and returns. This greatly helps other developers (and your future self!) understand and work with the code.
Better IDE Support: Modern Integrated Development Environments (IDEs) and code editors leverage type hints for features like autocompletion, refactoring, and inline error checking.
Refactoring Confidence: When refactoring code, type checkers can help ensure that changes don't introduce unintended type mismatches.
Basic Type Hinting Syntax:
Python's typing
module provides many tools for type hinting.
from typing import List, Tuple, Dict, Optional, Union, Any
# 1. Basic types
def greet(name: str) -> str:
return f"Hello, {name}!"
# 2. List of a specific type
def sum_numbers(numbers: List[int]) -> int:
return sum(numbers)
# 3. Tuple with specific types at each position
def get_coordinates() -> Tuple[float, float]:
return (10.5, 20.3)
# 4. Dictionary with specific key and value types
def get_user_info(user_id: int) -> Dict[str, str]:
users = {1: {'name': 'Alice', 'email': 'alice@example.com'}}
return users.get(user_id, {})
# 5. Optional type (can be a type or None)
def find_item(items: List[str], item: str) -> Optional[str]:
if item in items:
return item
return None
# 6. Union type (can be one of several types)
def process_input(value: Union[str, int]) -> str:
if isinstance(value, int):
return str(value * 2)
return value.upper()
# 7. Any type (use sparingly, means "any type")
def log_data(data: Any) -> None:
print(f"Logging: {data}")
# Example usage (no type checking here, just function calls)
print(greet("World"))
print(sum_numbers([1, 2, 3]))
print(get_coordinates())
print(get_user_info(1))
print(find_item(['apple', 'banana'], 'banana'))
print(process_input("hello"))
print(process_input(123))
log_data({'key': 'value'})
👉 Static Type Checking with mypy
mypy
is a popular static type checker for Python. It reads your code, analyzes the type hints, and reports any inconsistencies or potential type errors.
Installation:
pip install mypy
Basic Usage:
Let's create a file my_app.py
with some code, including a deliberate type error:
# my_app.py
from typing import List
def calculate_average(numbers: List[int]) -> float:
"""Calculates the average of a list of integers."""
if not numbers:
return 0.0
return sum(numbers) / len(numbers)
def process_data(data_list: List[str]) -> None:
"""Processes a list of strings."""
for item in data_list:
print(f"Processing: {item.upper()}")
# Correct usage
avg = calculate_average([10, 20, 30])
print(f"Average: {avg}")
process_data(["hello", "world"])
# Incorrect usage (type error)
# mypy should catch this!
process_data([1, 2, 3])
Now, run mypy
from your terminal in the same directory:
mypy my_app.py
Expected mypy
Output:
my_app.py:23: error: List item 0 has incompatible type "int"; expected "str" [list-item]
my_app.py:23: error: List item 1 has incompatible type "int"; expected "str" [list-item]
my_app.py:23: error: List item 2 has incompatible type "int"; expected "str" [list-item]
Found 3 errors in 1 file (checked 1 source file)
Common mypy
Flags:
--strict
: Enables all strict optional checks and warns about untyped definitions. Highly recommended for new projects.--ignore-missing-imports
: Ignores imports that `mypy` cannot find type information for. Useful when libraries don't have type stubs.--disallow-untyped-defs
: Reports an error whenever a function with no type annotations is encountered.--warn-unused-ignores
: Warns if a `# type: ignore` comment is not necessary.
Configuration (mypy.ini
or pyproject.toml
):
# mypy.ini
[mypy]
python_version = 3.9
warn_unused_configs = True
warn_redundant_casts = True
warn_unused_ignores = True
disallow_untyped_defs = True
disallow_incomplete_defs = True
no_implicit_optional = True
strict_optional = True
check_untyped_defs = True
show_error_codes = True
pretty = True
# Exclude specific files or directories
exclude = venv/|docs/|build/
# Per-module options (e.g., for a specific library you use)
[mypy-my_app.*]
ignore_missing_imports = True
Then, just run mypy
without specifying the file: “mypy .”
(it will find the config).
👉 Static Type Checking with pyright
pyright
is another powerful static type checker developed by Microsoft, primarily used with VS Code's Pylance extension, but also available as a standalone CLI tool. It's known for its speed and comprehensive type analysis.
Installation:
pip install pyright # As a Python package
Basic Usage:
Using the same my_app.py
file from before:
pyright my_app.py
Expected pyright
Output:
error: Argument of type "list[int]" cannot be assigned to parameter "data_list" of type "List[str]" in function "process_data"
"Literal[1]" is not assignable to "str"
"Literal[2]" is not assignable to "str"
"Literal[3]" is not assignable to "str" (reportArgumentType)
1 error, 0 warnings, 0 informations
Configuration (pyrightconfig.json
):
pyright
uses a pyrightconfig.json
file for configuration. Create this file in your project root:
{
"include": ["."], // Check all files in the current directory
"exclude": ["**/__pycache__", "venv", "docs", "build"],
"reportMissingImports": true,
"reportMissingTypeStubs": false, // Set to true if you want to be strict about stubs
"reportUnusedImport": true,
"reportUnusedVariable": true,
"pythonVersion": "3.9",
"typeCheckingMode": "strict" // "off", "basic", "strict"
}
Then, just run pyright
without specifying the file: pyright
(it will find the config).
Happy Typing