⚡IntermediatePython in Practice
Type Hints and Dataclasses
Adding type annotations for clarity and safety, and using @dataclass to eliminate boilerplate.
Overview
Python is dynamically typed variables don't have declared types at runtime. But since Python 3.5, you can add **type hints** (annotations) to functions and variables. They don't affect runtime behaviour Python ignores them by default but they enable:
- **Static analysis** with tools like mypy or Pyright that catch type errors before you run the code
- **Editor autocompletion** and inline error detection
- **Self-documenting code** the signature tells you exactly what goes in and what comes out
The `typing` module provides complex types: `Optional[T]`, `Union[T, U]`, `List[T]`, `Dict[K, V]`, `Tuple[T, ...]`, `Callable`, and more. Python 3.10+ supports `X | Y` union syntax directly.
**@dataclass** (Python 3.7+) auto-generates `__init__`, `__repr__`, and `__eq__` from class annotations eliminating the boilerplate of writing them manually. Add `frozen=True` for immutable dataclasses that also get `__hash__`.
Key Points
- •Type hints: `def fn(x: int, y: str) -> bool:` annotations, not enforced at runtime
- •`Optional[T]` is `Union[T, None]` the value can be T or None
- •Python 3.10+ union shorthand: `int | str` instead of `Union[int, str]`
- •`list[int]`, `dict[str, int]` (lowercase, Python 3.9+) no typing import needed
- •@dataclass auto-generates __init__, __repr__, __eq__ from field annotations
- •`field(default_factory=list)` for mutable defaults in dataclasses
- •`@dataclass(frozen=True)` makes instances immutable and hashable
Examples
Function and variable type hints
from typing import Optional, Union
def greet(name: str, times: int = 1) -> str:
return (f"Hello, {name}! " * times).strip()
def find_user(user_id: int) -> Optional[str]:
# Returns a name or None if not found
users = {1: "Alice", 2: "Bob"}
return users.get(user_id)
def process(value: Union[int, float]) -> float:
return float(value) * 2.0
# Variable annotations
scores: list[int] = [85, 90, 78]
config: dict[str, str] = {"host": "localhost", "port": "5432"}
result: Optional[str] = find_user(1)
print(result) # Alice
print(find_user(99)) # None@dataclass eliminating boilerplate
from dataclasses import dataclass, field
@dataclass
class Point:
x: float
y: float
def distance_from_origin(self) -> float:
return (self.x**2 + self.y**2) ** 0.5
# __init__, __repr__, __eq__ generated automatically
p1 = Point(3.0, 4.0)
p2 = Point(3.0, 4.0)
print(p1) # Point(x=3.0, y=4.0)
print(p1 == p2) # True
print(p1.distance_from_origin()) # 5.0
@dataclass
class Student:
name: str
grades: list[int] = field(default_factory=list) # mutable default
def average(self) -> float:
return sum(self.grades) / len(self.grades) if self.grades else 0.0
s = Student("Alice")
s.grades.extend([90, 85, 92])
print(s) # Student(name='Alice', grades=[90, 85, 92])
print(s.average()) # 89.0Frozen dataclasses and advanced typing
from dataclasses import dataclass
from typing import Callable, TypeVar
@dataclass(frozen=True) # immutable like a tuple with named fields
class Colour:
r: int
g: int
b: int
def to_hex(self) -> str:
return f"#{self.r:02X}{self.g:02X}{self.b:02X}"
red = Colour(255, 0, 0)
print(red.to_hex()) # #FF0000
# red.r = 128 # FrozenInstanceError can't modify
# Callable type hint
T = TypeVar("T")
def apply_twice(fn: Callable[[T], T], value: T) -> T:
return fn(fn(value))
print(apply_twice(lambda x: x * 2, 3)) # 12Common Mistakes
- Using mutable defaults in dataclass without field(default_factory=...) all instances would share the same list object
- Expecting type hints to enforce types at runtime they're only checked by static analysers, not Python itself
- Using the typing module's List/Dict/Tuple in Python 3.9+ prefer lowercase list[int], dict[str, int] which don't require an import
Note
Run `mypy yourfile.py` (install with pip) to catch type errors before running your code. Start by typing just the public API of your functions you get most of the safety benefit with minimal annotation effort.
Exercises
Write Python code to solve each exercise. Run it in your browser and check your output.
Loading editor…
Completed: 0 / 5 exercises
Knowledge Check
Question 1 of 4