Type hints and dataclasses
Type hints let you write down what types a function expects and returns. Python does not enforce them at runtime, but editors use them for autocomplete and warnings, and tools like mypy check them before you run anything. Dataclasses build on hints to remove boilerplate from simple classes.
Annotating variables and functions
def greet(name: str, times: int = 1) -> str:
return ("Hello, " + name + " ") * times
count: int = 3
print(greet("Ravi", count))The syntax is name: type for parameters and variables, and -> type for the return value.
Collection types
def average(values: list[float]) -> float:
return sum(values) / len(values)
def index_by_id(rows: list[dict[str, str]]) -> dict[str, dict[str, str]]:
return {row["id"]: row for row in rows}
print(average([1.5, 2.5]))
print(index_by_id([{"id": "a", "v": "1"}]))Since Python 3.9 you can use the built in names list, dict, tuple, set directly with square brackets.
Optional and union
def find(items: list[str], target: str) -> int | None:
return items.index(target) if target in items else None
print(find(["a", "b"], "b"))
print(find(["a", "b"], "z"))int | None means "an int or None". Older code writes Optional[int] from the typing module.
Other useful hints
from typing import Any, Callable, Iterable
def apply(f: Callable[[int], int], values: Iterable[int]) -> list[int]:
return [f(v) for v in values]
def log(payload: Any) -> None:
print(payload)
print(apply(lambda x: x * 2, range(4)))Type aliases
Point = tuple[float, float]
def distance(a: Point, b: Point) -> float:
return ((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2) ** 0.5
print(distance((0, 0), (3, 4)))Hints are not enforced
def double(n: int) -> int:
return n * 2
print(double("ab")) # runs fine, the hint is only adviceRun mypy yourfile.py to have the mismatch reported before it becomes a bug.
Dataclasses
For classes that mainly hold data, @dataclass writes __init__, __repr__ and __eq__ for you from the annotated fields.
from dataclasses import dataclass, field
@dataclass
class Student:
name: str
marks: list[int] = field(default_factory=list)
active: bool = True
def average(self) -> float:
return sum(self.marks) / len(self.marks) if self.marks else 0.0
s = Student("Neha", [88, 92])
print(s)
print(s.average())
print(s == Student("Neha", [88, 92]))Use field(default_factory=list) for mutable defaults; a plain = [] would be shared between instances.
Frozen dataclasses
frozen=True makes instances immutable and hashable, so they can be dictionary keys.
from dataclasses import dataclass
@dataclass(frozen=True)
class Coord:
lat: float
lon: float
c = Coord(26.9, 75.8)
print({c: "Jaipur"}[c])
try:
c.lat = 0
except Exception as e:
print(type(e).__name__)Ordering
order=True generates comparison methods field by field.
from dataclasses import dataclass
@dataclass(order=True)
class Version:
major: int
minor: int
print(sorted([Version(1, 10), Version(1, 2), Version(0, 9)]))Practice
- Add type hints to a function that takes a list of names and returns a dictionary from name to its length.
- Write a
Bookdataclass with title, author, year and an optional list of tags. Print two instances and compare them. - Make a frozen
Moneydataclass withamountandcurrencyand use it as a dictionary key.
