Skip to content

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
📱

Watch as a Web Story
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.

🔍 Free tool: requirements.txt Batch Health Checker — paste your requirements.txt and get instant health grades for all packages at once.

Founded

2023 in London, UK

Contact

hello@releaserun.com