Skip to content

Type Hints and Annotations

Document your code with type hints for better clarity and tooling support.

Type hints (also called type annotations) are a way to indicate the expected types of function parameters, return values, and variables. They help make code more readable, enable better IDE support, and allow static type checking tools to catch errors.

basic_annotations.py
def greet(name: str) -> str:
"""Function with type hints"""
return f"Hello, {name}!"
def calculate_total(price: float, quantity: int) -> float:
"""Calculate total with type hints"""
return price * quantity
# Usage
result = greet("Alice") # IDE knows result is str
total = calculate_total(19.99, 3) # IDE knows total is float
variable_annotations.py
# Variable type hints
username: str = "alice"
age: int = 25
price: float = 19.99
is_active: bool = True
# Type hints help IDEs provide better autocomplete and error detection
builtin_types.py
def process_data(
name: str,
age: int,
height: float,
is_student: bool,
score: float | None = None # Optional (Python 3.10+)
) -> str:
"""Function with various type hints"""
status = "student" if is_student else "not a student"
score_text = f" with score {score}" if score else ""
return f"{name}, age {age}, height {height}cm, {status}{score_text}"
# Usage
result = process_data("Alice", 25, 165.5, True, 95.5)
collections.py
from typing import List, Dict, Tuple, Set
def process_items(items: List[str]) -> List[str]:
"""Process a list of strings"""
return [item.upper() for item in items]
def get_user_data() -> Dict[str, int]:
"""Return a dictionary mapping strings to integers"""
return {"alice": 25, "bob": 30}
def get_coordinates() -> Tuple[float, float]:
"""Return a tuple of two floats"""
return (40.7128, -74.0060)
def unique_items(items: Set[str]) -> Set[str]:
"""Process a set of strings"""
return {item.upper() for item in items}
# Python 3.9+ you can use built-in types
def process_items_v2(items: list[str]) -> list[str]:
"""Using built-in list type (Python 3.9+)"""
return [item.upper() for item in items]
optional_types.py
from typing import Optional
def find_user(user_id: int) -> Optional[str]:
"""Return username or None if not found"""
users = {1: "alice", 2: "bob"}
return users.get(user_id)
# Python 3.10+ syntax
def find_user_v2(user_id: int) -> str | None:
"""Using | syntax (Python 3.10+)"""
users = {1: "alice", 2: "bob"}
return users.get(user_id)
union_types.py
from typing import Union
def process_value(value: Union[int, str]) -> str:
"""Accept either int or str"""
return str(value)
# Python 3.10+ syntax
def process_value_v2(value: int | str) -> str:
"""Using | syntax (Python 3.10+)"""
return str(value)
class_annotations.py
class User:
"""User class with type hints"""
def __init__(self, username: str, email: str, age: int):
self.username: str = username
self.email: str = email
self.age: int = age
self.is_active: bool = True
def get_info(self) -> str:
"""Return user information"""
status = "active" if self.is_active else "inactive"
return f"{self.username} ({self.email}), Age: {self.age}, Status: {status}"
# Usage
user = User("alice", "alice@example.com", 25)
print(user.get_info())
class_methods_annotations.py
class Calculator:
"""Calculator with type hints"""
@staticmethod
def add(a: float, b: float) -> float:
"""Add two numbers"""
return a + b
@classmethod
def multiply(cls, a: float, b: float) -> float:
"""Multiply two numbers"""
return a * b
def divide(self, a: float, b: float) -> float:
"""Divide two numbers"""
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
generic_types.py
from typing import TypeVar, Generic, List
T = TypeVar('T') # Generic type variable
class Stack(Generic[T]):
"""Generic stack class"""
def __init__(self):
self.items: List[T] = []
def push(self, item: T) -> None:
"""Push item onto stack"""
self.items.append(item)
def pop(self) -> T:
"""Pop item from stack"""
return self.items.pop()
def is_empty(self) -> bool:
"""Check if stack is empty"""
return len(self.items) == 0
# Usage
int_stack = Stack[int]()
int_stack.push(1)
int_stack.push(2)
str_stack = Stack[str]()
str_stack.push("hello")
str_stack.push("world")
callable_types.py
from typing import Callable
def apply_operation(
a: float,
b: float,
operation: Callable[[float, float], float]
) -> float:
"""Apply a function to two numbers"""
return operation(a, b)
def add(x: float, y: float) -> float:
return x + y
def multiply(x: float, y: float) -> float:
return x * y
# Usage
result1 = apply_operation(5, 3, add) # 8
result2 = apply_operation(5, 3, multiply) # 15
api_client_annotations.py
from typing import Dict, List, Optional, Any
from dataclasses import dataclass
@dataclass
class User:
"""User data class with type hints"""
id: int
username: str
email: str
age: int
class APIClient:
"""API client with comprehensive type hints"""
def __init__(self, base_url: str, api_key: str):
self.base_url: str = base_url
self.api_key: str = api_key
def get_user(self, user_id: int) -> Optional[User]:
"""Fetch a user by ID"""
# API call logic here
return User(id=user_id, username="alice", email="alice@example.com", age=25)
def get_all_users(self) -> List[User]:
"""Fetch all users"""
# API call logic here
return [
User(id=1, username="alice", email="alice@example.com", age=25),
User(id=2, username="bob", email="bob@example.com", age=30)
]
def create_user(self, user_data: Dict[str, Any]) -> User:
"""Create a new user"""
# API call logic here
return User(
id=user_data.get("id", 0),
username=user_data["username"],
email=user_data["email"],
age=user_data["age"]
)
# Usage
client = APIClient("https://api.example.com", "api_key_123")
user = client.get_user(1)
users = client.get_all_users()

Type hints don’t affect runtime behavior, but you can use tools like mypy to check types:

Terminal window
# Install mypy
pip install mypy
# Check your code
mypy your_file.py
type_errors.py
def add_numbers(a: int, b: int) -> int:
return a + b
# These would be caught by mypy:
# result = add_numbers("hello", "world") # Type error!
# result = add_numbers(5, "world") # Type error!
result = add_numbers(5, 3) # OK