Type Hints
Python is dynamically typed, but type hints let you annotate expected types. They don't affect runtime behavior โ they're for documentation, IDE assistance, and static analysis tools like mypy.
# Basic type annotations (Python 3.5+)
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 | None: # Python 3.10+
if b == 0:
return None
return a / b
# Variable annotations
x: int = 42
name: str = "Alice"
items: list = []
# Modern type syntax (Python 3.9+ for built-ins, 3.10+ for |)
def process(items: list[str]) -> dict[str, int]:
return {item: len(item) for item in items}
# Before 3.9, import from typing
from typing import List, Dict, Optional, Tuple, Set
def old_style(items: List[str]) -> Dict[str, int]:
return {item: len(item) for item in items}Common Type Annotations
from typing import Optional, Union, Any, Callable, Sequence
# Optional โ can be None
def find_user(id: int) -> Optional[str]: # str | None
return "Alice" if id == 1 else None
# Union โ multiple types
def process(data: Union[str, bytes]) -> str: # str | bytes
return data.decode() if isinstance(data, bytes) else data
# Callable โ function type
def apply(func: Callable[[int, int], int], a: int, b: int) -> int:
return func(a, b)
# Sequence โ ordered collection (list, tuple, string)
def first(items: Sequence[int]) -> int:
return items[0]
# TypeVar โ generic types
from typing import TypeVar
T = TypeVar('T')
def identity(x: T) -> T:
return x # Return type is same as input typeClasses and TypedDict
from typing import TypedDict, Protocol
from dataclasses import dataclass
# TypedDict โ typed dictionary
class UserDict(TypedDict):
name: str
age: int
email: str
def process_user(user: UserDict) -> str:
return f"{user['name']} ({user['age']})"
# Protocol โ structural typing (duck typing formalized)
class Drawable(Protocol):
def draw(self) -> None: ...
def render(shape: Drawable) -> None:
shape.draw() # Works with any object that has draw()
# dataclass with type hints (already covered!)
@dataclass
class Point:
x: float
y: floatRunning mypy
# pip install mypy
# Check a file
mypy my_module.py
# Strict mode
mypy --strict my_module.py
# Common mypy settings in pyproject.toml:
# [tool.mypy]
# python_version = "3.11"
# strict = true
# ignore_missing_imports = true
# Example errors mypy catches:
def add(a: int, b: int) -> int:
return a + b
add("hello", "world") # mypy error: Argument 1 has incompatible type "str"; expected "int"
result: int = add(1.5, 2.5) # mypy error: Incompatible types in assignmentKey Takeaways
- Type hints are optional but valuable: documentation + IDE support + static analysis
- Modern syntax (3.10+):
str | Noneinstead ofOptional[str] - Built-in generics (3.9+):
list[str]instead ofList[str] - mypy for static checking: catches type errors before runtime
- Type hints don't run at runtime: they're stripped by Python unless you check them explicitly
Practice Exercises
- Add type hints to all functions in a previous project. Run mypy and fix all errors.
- Write a typed generic
Stack[T]class using TypeVar. - Create a TypedDict for an API response structure. Write a function that parses and returns it.