Python Reference
Python Reference
Modern Python 3.10–3.13: comprehensions, dataclasses, context managers, itertools, pathlib, pattern matching, and the standard library modules you reach for every week.
Comprehensions — list, dict, set, generator
# List comprehension
squares = [x**2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]
flat = [item for sublist in nested for item in sublist]
matrix = [[i*j for j in range(1, 4)] for i in range(1, 4)]
# Dict comprehension
inverted = {v: k for k, v in original.items()}
squared = {x: x**2 for x in range(5)}
filtered = {k: v for k, v in data.items() if v is not None}
# Set comprehension
unique_len = {len(word) for word in words}
# Generator expression — lazy, memory-efficient
total = sum(x**2 for x in range(1_000_000))
first_even = next(x for x in numbers if x % 2 == 0)
# Walrus operator (:=) — assign inside expression (Python 3.8+)
if n := len(data):
print(f"Got {n} items")
filtered = [y for x in data if (y := transform(x)) is not None]
while chunk := file.read(8192):
process(chunk)
Dataclasses — modern class patterns
from dataclasses import dataclass, field, asdict, astuple
from typing import ClassVar
@dataclass
class Point:
x: float
y: float
z: float = 0.0 # default value
def distance(self) -> float:
return (self.x**2 + self.y**2 + self.z**2) ** 0.5
# Frozen — immutable, hashable (can use as dict key / in sets)
@dataclass(frozen=True)
class Coordinate:
lat: float
lon: float
# Mutable default — must use field(default_factory=...)
@dataclass
class Config:
tags: list[str] = field(default_factory=list)
meta: dict[str, str] = field(default_factory=dict)
_cache: dict = field(default_factory=dict, repr=False, compare=False)
MAX_SIZE: ClassVar[int] = 100 # class variable, not an instance field
# __post_init__ for validation
@dataclass
class User:
name: str
email: str
role: str = "user"
def __post_init__(self):
self.email = self.email.lower()
if self.role not in ("user", "admin"):
raise ValueError(f"Invalid role: {self.role}")
# Serialisation
user = User("Alice", "ALICE@EXAMPLE.COM")
asdict(user) # {"name": "Alice", "email": "alice@example.com", "role": "user"}
astuple(user) # ("Alice", "alice@example.com", "user")
# Inheritance
@dataclass
class AdminUser(User):
permissions: list[str] = field(default_factory=list)
# Order — enables <, >, == comparisons
@dataclass(order=True)
class Version:
major: int
minor: int
patch: int = 0
Context managers — with statement
from contextlib import contextmanager, asynccontextmanager, suppress, ExitStack
# contextmanager decorator — simplest way to write one
@contextmanager
def timer(label: str):
import time
start = time.perf_counter()
try:
yield # runs the with-block
finally:
elapsed = time.perf_counter() - start
print(f"{label}: {elapsed:.3f}s")
with timer("query"):
result = db.execute(sql)
# Class-based context manager
class TempDir:
def __enter__(self):
import tempfile
self.path = tempfile.mkdtemp()
return self.path
def __exit__(self, exc_type, exc_val, exc_tb):
import shutil
shutil.rmtree(self.path)
return False # True would suppress exceptions
# contextlib.suppress — ignore specific exceptions
with suppress(FileNotFoundError):
os.remove("/tmp/stale.lock")
# ExitStack — dynamic number of context managers
with ExitStack() as stack:
files = [stack.enter_context(open(f)) for f in filenames]
process(files)
# Multiple context managers (Python 3.10+ — parenthesised)
with (
open("input.txt") as fin,
open("output.txt", "w") as fout,
timer("copy"),
):
fout.write(fin.read())
# Async context manager
@asynccontextmanager
async def db_transaction(conn):
async with conn.transaction():
try:
yield conn
except Exception:
await conn.rollback()
raise
Itertools — the iterator toolkit
import itertools as it
# Infinite iterators
it.count(10, 2) # 10, 12, 14, ...
it.cycle([1, 2, 3]) # 1, 2, 3, 1, 2, 3, ...
it.repeat("x", 3) # "x", "x", "x"
# Combinatorics
it.combinations([1,2,3,4], 2) # (1,2), (1,3), (1,4), (2,3), ...
it.permutations("abc", 2) # ('a','b'), ('a','c'), ('b','a'), ...
it.combinations_with_replacement("AB", 2) # AA, AB, BB
it.product([1,2], ["a","b"]) # (1,'a'), (1,'b'), (2,'a'), (2,'b')
# Slicing / windowing
it.islice(gen, 5) # first 5 items from generator (no memory overhead)
it.islice(gen, 2, 10, 2) # start=2, stop=10, step=2
# Grouping
for key, group in it.groupby(sorted_items, key=lambda x: x.category):
print(key, list(group)) # NOTE: input must be sorted by key!
# Chaining
it.chain([1,2], [3,4], [5]) # 1, 2, 3, 4, 5
it.chain.from_iterable([[1,2],[3]]) # same but from iterable of iterables
# Filtering
it.takewhile(lambda x: x < 5, [1,2,3,7,2]) # 1, 2, 3
it.dropwhile(lambda x: x < 5, [1,2,3,7,2]) # 7, 2
it.compress("ABCDE", [1,0,1,0,1]) # A, C, E
it.filterfalse(str.isdigit, "a1b2c3") # a, b, c
# Accumulate
import operator
it.accumulate([1,2,3,4], operator.add) # 1, 3, 6, 10 (running total)
it.accumulate([1,2,3,4], max) # 1, 2, 3, 4
# Zip longest (fills missing with fillvalue)
it.zip_longest([1,2,3], ["a","b"], fillvalue=None)
# (1,'a'), (2,'b'), (3, None)
# Pairwise (Python 3.10+)
it.pairwise([1, 2, 3, 4]) # (1,2), (2,3), (3,4)
# Batched (Python 3.12+) — split into chunks
list(it.batched(range(10), 3)) # [(0,1,2), (3,4,5), (6,7,8), (9,)]
pathlib — file system operations
from pathlib import Path
# Basics
p = Path("/home/user/project")
p = Path.home() / "project" / "data" # / operator for joining
p = Path.cwd() # current working directory
# Path components
p.name # "data.csv"
p.stem # "data"
p.suffix # ".csv"
p.suffixes # [".tar", ".gz"] for "archive.tar.gz"
p.parent # /home/user/project
p.parents[1] # /home/user
p.parts # ('/', 'home', 'user', 'project', 'data')
# Tests
p.exists()
p.is_file()
p.is_dir()
p.is_symlink()
p.is_absolute()
# Read / write
text = p.read_text(encoding="utf-8")
data = p.read_bytes()
p.write_text("content", encoding="utf-8")
p.write_bytes(b"raw")
# Listing
for f in p.iterdir(): # direct children
print(f)
list(p.glob("*.py")) # glob pattern (non-recursive)
list(p.glob("**/*.py")) # recursive
list(p.rglob("*.py")) # rglob = recursive glob shorthand
# Create / delete
p.mkdir(parents=True, exist_ok=True) # mkdir -p
p.unlink(missing_ok=True) # delete file
p.rmdir() # delete empty dir
import shutil; shutil.rmtree(p) # delete directory tree
# Copy / rename
import shutil
shutil.copy2(src, dst) # copy with metadata
p.rename(new_path) # rename / move (same filesystem)
shutil.move(src, dst) # move across filesystems
# Stat / permissions
stat = p.stat()
stat.st_size # bytes
stat.st_mtime # modification time (float)
import os
p.chmod(0o644)
# Resolve symlinks and relative paths
p.resolve() # absolute path, resolves symlinks
p.relative_to(base) # relative path from base
# Open as file object
with p.open("r", encoding="utf-8") as f:
content = f.read()
# Common patterns
config_dir = Path.home() / ".config" / "myapp"
config_dir.mkdir(parents=True, exist_ok=True)
log_files = sorted(Path("/var/log").glob("app.*.log"),
key=lambda f: f.stat().st_mtime, reverse=True)
Pattern matching — match/case (Python 3.10+)
def handle(event: dict):
match event:
# Literal match
case {"type": "click", "x": x, "y": y}:
print(f"Click at {x}, {y}")
# OR patterns
case {"type": "keydown" | "keyup", "key": key}:
print(f"Key event: {key}")
# Wildcard — _ matches and discards
case {"type": _}:
print("Unknown event type")
# Class pattern matching
match shape:
case Circle(radius=r) if r > 0:
area = 3.14159 * r * r
case Rectangle(width=w, height=h):
area = w * h
case _:
raise ValueError("Unknown shape")
# Sequence patterns
match command.split():
case ["quit"]:
quit()
case ["go", direction] if direction in ("north", "south"):
move(direction)
case ["go", *rest]:
print(f"Cannot go {rest}")
case ["get", obj, "from", container]:
pick_up(obj, container)
# Capture groups
match point:
case (0, 0):
print("origin")
case (x, 0):
print(f"on x-axis at {x}")
case (0, y):
print(f"on y-axis at {y}")
case (x, y):
print(f"at {x}, {y}")
# Guards
match temperature:
case t if t < 0: print("freezing")
case t if t < 20: print("cold")
case t if t < 30: print("comfortable")
case t: print(f"hot: {t}")
String methods — the most useful ones
# Test methods
s.startswith("prefix")
s.endswith(".py")
s.startswith(("a", "b", "c")) # tuple of options
s.isdigit()
s.isalpha()
s.isalnum()
s.isspace()
s.isupper()
s.islower()
# Strip
s.strip() # both ends
s.lstrip(" \t") # left only
s.rstrip("\n") # right only
s.removeprefix("src/") # Python 3.9+ — only removes if present
s.removesuffix(".py") # Python 3.9+
# Split / join
s.split(",") # split on delimiter
s.split(",", maxsplit=2)
s.splitlines() # split on \n, \r\n, \r
", ".join(items) # join list — FAR faster than += in loop
# Find / replace
s.find("needle") # index or -1
s.index("needle") # index or ValueError
s.count("x")
s.replace("old", "new")
s.replace("old", "new", count=1) # replace first N occurrences
# Case
s.upper()
s.lower()
s.title() # "hello world" → "Hello World"
s.capitalize() # "hello" → "Hello" (lowercases rest)
s.casefold() # aggressive lowercase for comparisons ("ß" → "ss")
s.swapcase()
# Pad / align
s.ljust(20) # left-align in 20 chars
s.rjust(20, "0") # right-align, pad with "0"
s.center(20, "-")
s.zfill(8) # "42" → "00000042"
# f-string formatting
f"{value:.2f}" # 2 decimal places
f"{value:,}" # thousands separator: 1,234,567
f"{value:>10}" # right-align in 10 chars
f"{value:#010x}" # hex with 0x prefix, zero-padded: 0x0000002a
f"{value!r}" # repr()
f"{value!s}" # str()
f"{datetime.now():%Y-%m-%d}" # date formatting
# Python 3.12+: f"{value=}" prints "value=42" (debug f-strings)
functools — caching, partial, reduce
import functools
# lru_cache — memoize function calls
@functools.lru_cache(maxsize=128)
def fib(n: int) -> int:
if n < 2: return n
return fib(n-1) + fib(n-2)
fib.cache_info() # CacheInfo(hits=..., misses=..., maxsize=128, currsize=...)
fib.cache_clear() # clear the cache
# cache — same as lru_cache(maxsize=None) — Python 3.9+
@functools.cache
def expensive(key: str) -> str:
return fetch_from_api(key)
# cached_property — compute once, then stored as instance attribute
class Circle:
def __init__(self, radius):
self.radius = radius
@functools.cached_property
def area(self):
import math
return math.pi * self.radius ** 2
# partial — fix some arguments of a function
from functools import partial
def power(base, exponent): return base ** exponent
square = partial(power, exponent=2)
cube = partial(power, exponent=3)
square(5) # 25
log_error = partial(print, "[ERROR]", end="\n", flush=True)
# reduce — fold a sequence
from functools import reduce
product = reduce(lambda acc, x: acc * x, [1, 2, 3, 4, 5]) # 120
merged = reduce(lambda a, b: {**a, **b}, list_of_dicts)
# total_ordering — define <, and get <=, >, >= automatically
@functools.total_ordering
class Version:
def __init__(self, major, minor):
self.major, self.minor = major, minor
def __eq__(self, other): return (self.major, self.minor) == (other.major, other.minor)
def __lt__(self, other): return (self.major, self.minor) < (other.major, other.minor)
# wraps — preserve metadata when writing decorators
def retry(times):
def decorator(func):
@functools.wraps(func) # preserve __name__, __doc__, __annotations__
def wrapper(*args, **kwargs):
for attempt in range(times):
try: return func(*args, **kwargs)
except Exception:
if attempt == times - 1: raise
return wrapper
return decorator
collections — Counter, defaultdict, deque, namedtuple
from collections import Counter, defaultdict, deque, namedtuple, OrderedDict, ChainMap
# Counter — count occurrences
words = Counter("abracadabra".split())
counts = Counter(["apple", "banana", "apple", "cherry", "apple"])
counts["apple"] # 3
counts.most_common(2) # [("apple", 3), ("banana", 1)]
counts.total() # Python 3.10+
counts.update(["apple"]) # add more
counts - Counter(["apple"]) # subtract
# defaultdict — auto-create missing keys
graph = defaultdict(list)
graph["a"].append("b") # no KeyError even if "a" didn't exist
groups = defaultdict(set)
counter = defaultdict(int)
counter["key"] += 1 # no KeyError
# deque — O(1) append/pop from both ends
dq = deque([1, 2, 3], maxlen=5)
dq.appendleft(0) # [0, 1, 2, 3]
dq.append(4) # [0, 1, 2, 3, 4]
dq.popleft() # 0, deque is now [1, 2, 3, 4]
dq.pop() # 4
dq.rotate(2) # rotate right by 2
# namedtuple — lightweight immutable struct
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
p.x # 3
p._asdict() # OrderedDict([('x', 3), ('y', 4)])
p._replace(x=10) # new Point(10, 4)
# typing.NamedTuple — typed version (preferred)
from typing import NamedTuple
class Employee(NamedTuple):
name: str
department: str
salary: float = 50_000.0
# ChainMap — search multiple dicts in order
import os
env = ChainMap({"DEBUG": "1"}, os.environ) # local overrides win
env["PATH"] # falls through to os.environ if not in first dict
Error handling and exceptions
# Exception groups (Python 3.11+) — handle multiple exceptions
try:
async with asyncio.TaskGroup() as tg:
tg.create_task(fetch_a())
tg.create_task(fetch_b())
except* ValueError as eg:
for exc in eg.exceptions:
print("ValueError:", exc)
except* IOError as eg:
print(f"Got {len(eg.exceptions)} IO errors")
# except* raises ExceptionGroup if unhandled exceptions remain
# Exception notes (Python 3.11+) — add context to exceptions
try:
process(record)
except ValueError as e:
e.add_note(f"While processing record id={record.id}")
raise
# Exception chaining
try:
json.loads(raw)
except json.JSONDecodeError as e:
raise ValueError(f"Invalid config: {raw[:50]}") from e
# Suppress chaining with "from None"
try:
return cache[key]
except KeyError:
raise LookupError(key) from None # hides the KeyError
# Custom exception hierarchy
class AppError(Exception):
def __init__(self, message, code=None):
super().__init__(message)
self.code = code
class ValidationError(AppError): pass
class NotFoundError(AppError): pass
# Context manager for exception translation
from contextlib import contextmanager
@contextmanager
def translate_db_errors():
try:
yield
except IntegrityError as e:
raise ValidationError("Constraint violation") from e
except OperationalError as e:
raise AppError("Database unavailable", code="DB_DOWN") from e
typing — type hints reference
from typing import (
Optional, Union, Any, Literal, Final,
TypeVar, Generic, Protocol, overload,
TypedDict, NamedTuple, TypeAlias, ParamSpec
)
from collections.abc import Callable, Iterator, Generator, Sequence, Mapping
# Basic annotations
def greet(name: str, times: int = 1) -> str: ...
# Container types (Python 3.9+ — use built-ins directly)
items: list[str]
lookup: dict[str, int]
pair: tuple[int, str]
unique: set[float]
nested: list[dict[str, list[int]]]
# Optional and Union
# Python 3.10+ syntax (preferred)
def find(id: int) -> User | None: ...
def parse(val: str | int | float) -> float: ...
# Older syntax
def find(id: int) -> Optional[User]: ... # Optional[X] = X | None
def parse(val: Union[str, int, float]) -> float: ...
# Literal — restrict to specific values
def log(level: Literal["debug", "info", "warning", "error"]): ...
Status: TypeAlias = Literal["pending", "active", "closed"]
# TypedDict — dict with known structure
class UserDict(TypedDict):
id: int
name: str
email: str
role: str # required by default
class PartialUser(TypedDict, total=False):
id: int # all optional
# Callable
Handler = Callable[[Request], Response]
Transform = Callable[[str], str]
VarArgs = Callable[..., int] # any args, returns int
# TypeVar — generic functions
T = TypeVar("T")
def first(items: list[T]) -> T | None:
return items[0] if items else None
# Protocol — structural subtyping (duck typing with types)
class Closeable(Protocol):
def close(self) -> None: ...
def cleanup(resource: Closeable) -> None:
resource.close() # any object with close() works
# ParamSpec (Python 3.10+) — preserve signature through decorators
P = ParamSpec("P")
def logged(func: Callable[P, T]) -> Callable[P, T]:
@functools.wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
Track Python EOL dates and releases at ReleaseRun.
📱
Watch as a Web Story
5 Python Security Mistakes That Expose Your Application — quick visual guide, 2 min
5 Python Security Mistakes That Expose Your Application — quick visual guide, 2 min
📱
Watch as a Web Story
Python 3.14 Is Coming: 5 Changes That Actually Matter — quick visual guide, 2 min
Python 3.14 Is Coming: 5 Changes That Actually Matter — quick visual guide, 2 min
📦 Audit your Python deps: Use the PyPI Package Health Checker to find deprecated, abandoned, or stale packages in your requirements.txt.
Founded
2023 in London, UK
Contact
hello@releaserun.com