Skip to content

Python

Python emphasizes readable code, dynamic typing, a large standard library, and multiple programming styles. Use the maintained Python tutorial as the language reference; this chapter focuses on engineering choices.

Values and Collections

name = "Ada"                 # str
attempts = 3                 # int
ratio = 0.75                 # float
enabled = True               # bool
missing = None               # absence

items = ["a", "b"]          # ordered, mutable
point = (10, 20)             # ordered, immutable
roles = {"reader", "writer"} # unique values
user = {"name": name, "roles": roles}

Names refer to objects. Assignment does not copy an object; two names can refer to the same mutable list or dictionary. Copy deliberately, and avoid mutable default arguments.

def append_item(item: str, items: list[str] | None = None) -> list[str]:
    result = [] if items is None else items
    result.append(item)
    return result

Use == for value equality and is for identity, chiefly value is None. Strings are Unicode; bytes are raw octets. Encode and decode explicitly at boundaries.

Control Flow and Iteration

for index, value in enumerate(items):
    if value.startswith("a"):
        print(index, value)

squares = [number * number for number in range(10) if number % 2 == 0]

Iterators produce one value at a time; generators make lazy pipelines and reduce memory use:

from collections.abc import Iterable, Iterator


def positive(values: Iterable[int]) -> Iterator[int]:
    for value in values:
        if value > 0:
            yield value

Do not consume an iterator twice unless it is explicitly replayable.

Functions and Types

def total(prices: list[int], *, tax_rate: float = 0.0) -> int:
    """Return the rounded total in the same minor currency unit."""
    return round(sum(prices) * (1 + tax_rate))

Keyword-only parameters clarify booleans and options. Type annotations document contracts and support static tools; Python still validates nothing automatically at runtime. Validate untrusted input at the boundary.

Functions are values and closures capture surrounding names. Prefer a plain function to a one-method class. Use decorators only when wrapping behavior is clearer than an explicit call.

Data Classes and Objects

from dataclasses import dataclass


@dataclass(frozen=True, slots=True)
class Money:
    cents: int
    currency: str

    def add(self, other: "Money") -> "Money":
        if self.currency != other.currency:
            raise ValueError("currency mismatch")
        return Money(self.cents + other.cents, self.currency)

Use classes when state and behavior form a meaningful abstraction. Data classes reduce record boilerplate. Prefer composition over deep inheritance, and expose behavior rather than every internal field.

Protocols describe structural interfaces without forcing inheritance:

from typing import Protocol


class Writer(Protocol):
    def write(self, data: bytes) -> int: ...

Errors and Cleanup

from pathlib import Path


def read_count(path: Path) -> int:
    try:
        return int(path.read_text().strip())
    except FileNotFoundError:
        return 0
    except ValueError as error:
        raise ValueError(f"invalid count in {path}") from error

Catch the narrow exceptions you can handle. Do not use exceptions for ordinary expected branching when a return value is clearer. Preserve causal context with raise ... from ....

Context managers guarantee structured cleanup:

with Path("output.txt").open("w", encoding="utf-8") as handle:
    handle.write("done\n")

Modules and Projects

Put reusable code in importable modules and executable entry behavior behind:

def main() -> int:
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

Avoid modifying sys.path inside application code and avoid wildcard imports. Keep dependencies declared and locked according to the project tool. Use pyproject.toml for modern project metadata and follow the living Python Packaging User Guide.

Files, JSON, and Serialization

Use pathlib for paths and specify text encoding. JSON is interoperable but supports a limited type set:

import json
from pathlib import Path

path = Path("record.json")
path.write_text(json.dumps({"id": 7}), encoding="utf-8")
record = json.loads(path.read_text(encoding="utf-8"))

Never deserialize untrusted pickle data; loading it can execute code. Use a schema and validate input for durable or external formats.

Concurrency

  • threads suit blocking I/O and shared-memory integrations;
  • asyncio suits many cooperative I/O operations when libraries are async-aware;
  • processes suit CPU-bound Python work and isolation;
  • native libraries may perform parallel work outside Python execution constraints.
import asyncio


async def fetch_all(urls: list[str]) -> list[str]:
    async with asyncio.TaskGroup() as group:
        tasks = [group.create_task(fetch(url)) for url in urls]
    return [task.result() for task in tasks]

fetch is intentionally application-specific. Bound concurrency, apply timeouts, propagate cancellation, and avoid blocking calls in an event loop. Concurrency does not make unsafe shared mutation correct.

Standard Library First

Before adding a dependency, check pathlib, collections, itertools, functools, contextlib, dataclasses, enum, datetime, decimal, statistics, json, sqlite3, subprocess, and concurrent.futures. Add a library when it materially improves correctness or capability, not to replace a few clear standard operations.

Testing and Debugging

Use assertions in tests, not for validating untrusted production input because assertions can be disabled. Prefer deterministic functions, dependency injection at real boundaries, and small reproductions. Inspect exceptions and state before adding logging everywhere.

Profile before optimizing; Python-level object allocation, I/O, database access, algorithms, and serialization often matter more than syntax tricks.

Checklist

  • Are mutable objects and ownership clear?
  • Are boundaries typed and validated at runtime where needed?
  • Are exceptions narrow and cleanup structured?
  • Are files encoded explicitly and serialization safe?
  • Is concurrency bounded with timeout and cancellation?
  • Is a standard-library feature sufficient?
  • Can the module be imported without unexpected side effects?