🕯️ Magic Note
Type hints were introduced in Python 3.5 (PEP 484). They are entirely optional. You can add them gradually to existing code. They make your code more readable and help IDEs provide better autocomplete and refactoring support.
- **Catch bugs early:** mypy finds type errors without running the code
- **Better IDE support:** autocomplete, refactoring, and warnings
- **Self-documenting code:** types act as documentation
- **Easier refactoring:** change a type and mypy shows everywhere it breaks
- **Better team collaboration:** new developers understand expected types
Python
# Variable type hints (Python 3.6+)
name: str = “Feloriya”
age: int = 25
price: float = 19.99
is_active: bool = True
# Variable without initial value (Python 3.6+)
user_id: int
user_id = 42
# Function parameter and return type hints
def greet(name: str) -> str:
return f”Hello, {name}!”
def add(a: int, b: int) -> int:
return a + b
def divide(a: float, b: float) -> float:
return a / b
Python
from typing import List, Dict, Tuple, Set, Optional, Union, Any
# List of strings
names: List[str] = [“Ali”, “Sara”, “Reza”]
# Dictionary with string keys and integer values
scores: Dict[str, int] = {“Ali”: 95, “Sara”: 87}
# Tuple of string and integer
person: Tuple[str, int] = (“Ali”, 25)
# Set of integers
unique_numbers: Set[int] = {1, 2, 3}
# Nested collections
matrix: List[List[int]] = [[1, 2], [3, 4]]
# Function with collection types
def process_names(names: List[str]) -> List[str]:
return [name.upper() for name in names]
🕯️ Magic Note
In Python 3.9+, you can use built-in types directly without importing from typing: list[str], dict[str, int], tuple[str, int]. This is cleaner and recommended.
Python (Python 3.9+)
# Python 3.9+ syntax (no typing import needed for basics)
names: list[str] = [“Ali”, “Sara”, “Reza”]
scores: dict[str, int] = {“Ali”: 95, “Sara”: 87}
person: tuple[str, int] = (“Ali”, 25)
unique_numbers: set[int] = {1, 2, 3}
Python
from typing import Optional, Union
# Optional means the value can be None or the specified type
def find_user(user_id: int) -> Optional[str]:
if user_id == 1:
return “Ali”
return None # Valid because return type is Optional[str]
# Optional[T] is equivalent to Union[T, None]
def get_config(key: str) -> Union[str, int, None]:
config = {“host”: “localhost”, “port”: 8080}
return config.get(key)
# Union of multiple types (Python 3.10+ has cleaner syntax)
def process(value: Union[int, str]) -> str:
return str(value)
🕯️ Magic Note
Python 3.10 introduced a cleaner syntax for unions: int | str instead of Union[int, str]. Python 3.10+ also supports int | None instead of Optional[int].
Python (Python 3.10+)
# Python 3.10+ union syntax
def process(value: int | str) -> str:
return str(value)
def find_user(user_id: int) -> str | None:
if user_id == 1:
return “Ali”
return None
Python
from typing import List, Dict, Tuple
# Define type aliases
UserId = int
UserScore = float
UserScores = Dict[UserId, UserScore]
Coordinate = Tuple[float, float]
# Use aliases
scores: UserScores = {1: 95.5, 2: 87.0}
point: Coordinate = (10.5, 20.3)
def get_average(scores: UserScores) -> UserScore:
return sum(scores.values()) / len(scores)
Python
from typing import Any
# Any means “no type checking” (mypy will ignore this value)
def flexible(data: Any) -> Any:
return data
# Use Any sparingly. It defeats the purpose of type checking.
Bash
# Install mypy
pip install mypy
# Run mypy on a file
mypy my_script.py
# Run mypy on entire project
mypy .
# Ignore missing imports (for third-party libraries without types)
mypy –ignore-missing-imports my_script.py
Python (Example with a type error)
def greet(name: str) -> str:
return f”Hello, {name}!”
# This will cause a mypy error
greet(42) # Argument 1 to “greet” has incompatible type “int”; expected “str”
# mypy output:
# error: Argument 1 to “greet” has incompatible type “int”; expected “str”
# Found 1 error in 1 file (checked 1 source file)
🕯️ Magic Note
mypy catches type errors without running your code. It analyzes your type hints and the actual values passed. This is static type checking, like in languages such as Java or TypeScript, but optional in Python.
Python
# Before (no type hints)
def calculate_total(prices, tax_rate):
subtotal = sum(prices)
tax = subtotal * tax_rate
return subtotal + tax
# After (with type hints)
from typing import List
def calculate_total(prices: List[float], tax_rate: float) -> float:
subtotal = sum(prices)
tax = subtotal * tax_rate
return subtotal + tax
# Now mypy can catch errors like:
# calculate_total([10, 20, “30”], 0.1) # error: List item has incompatible type “str”
Python
from typing import List, Optional
class Student:
def __init__(self, name: str, student_id: int) -> None:
self.name: str = name
self.student_id: int = student_id
self.grades: List[float] = []
def add_grade(self, grade: float) -> None:
if 0 <= grade <= 100:
self.grades.append(grade)
else:
raise ValueError(f”Invalid grade: {grade}”)
def get_average(self) -> Optional[float]:
if not self.grades:
return None
return sum(self.grades) / len(self.grades)
def __str__(self) -> str:
return f”Student(name={self.name}, id={self.student_id})”
INI (mypy.ini)
[mypy]
python_version = 3.11
warn_return_any = True
warn_unused_configs = True
disallow_untyped_defs = True
ignore_missing_imports = True
[mypy-my_package.*]
disallow_untyped_defs = True
[mypy-tests.*]
disallow_untyped_defs = False
Python
def safe_divide(a: float, b: float) -> float:
# Runtime check (not guaranteed by type hints)
if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
raise TypeError(“Both arguments must be numbers”)
if b == 0:
raise ValueError(“Cannot divide by zero”)
return a / b
- Assuming type hints enforce types at runtime (they do not)
- Overusing Any (defeats purpose of type checking)
- Forgetting to run mypy (type hints alone do nothing)
- Adding type hints to code that will never be checked
- Using Optional when Union[T, None] is intended
- Write a type hint for a list of integers.
- What is the difference between Optional[int] and int?
- How do you run mypy on a Python file?
- Write a function signature that takes a string and returns an optional integer.
- What is a type alias and when would you use it?
⚡ Whisper
Type hints are optional. Python will run without them. But optional does not mean useless. Type hints are documentation that never goes out of date. They are IDE superpowers. They are mypy’s eyes. They catch the mistakes you would find at 2 AM. Adding def greet(name: str) -> str: takes a second. It saves hours. Start with function signatures. Add List[str] for collections. Add Optional for None. Run mypy. Fix the errors. Then commit. Your future self will thank you. Your teammates will thank you. The code will be clearer, safer, and more professional. Type hints are not burden. They are clarity. Use them.