This page covers the tools for structuring Python once a script grows: lazy iteration, decorators, handling errors and releasing resources, organizing code into modules and classes, typing it, and testing it.

Iterators and generators

An iterable produces an iterator, and an iterator yields one value at a time. Calling a generator function runs none of its body: it returns a paused object that runs up to the next yield each time a value is requested and raises StopIteration when it finishes.

sequenceDiagram
    accTitle: Generator pause and resume
    accDescr: Calling the generator function returns a paused object without running code. Each next call resumes until the next yield; reaching the end raises StopIteration.
    participant Caller
    participant Gen as Generator
    Caller->>Gen: server_errors(lines)
    Gen-->>Caller: paused generator
    Caller->>Gen: next(gen)
    Gen-->>Caller: value from first yield
    Caller->>Gen: next(gen)
    Gen-->>Caller: StopIteration at the end

Generator expressions such as sum(n * n for n in values) are lazy too. They suit streams and large inputs, but an iterator is normally consumed only once.1

Decorators

@trace above def deploy is shorthand for deploy = trace(deploy): the name is rebound to the wrapper that trace returns, so each call reaches the wrapper first, which then calls the original. Use functools.wraps so the wrapper keeps the original name and docstring, and prefer a direct call when the behavior is not shared across several functions.2

Exceptions

Exactly one of except or else runs, depending on whether an exception occurred, and finally always runs. Catch the narrowest expected exception, re-raise with context using raise RuntimeError(...) from error, never swallow errors silently, and avoid a bare except: unless you must also intercept process exit and cancellation.3

Context managers and files

A with block runs the entry step, then the body, and then the exit step exactly once, even if the body raises.

flowchart LR
    accTitle: with-statement guaranteed cleanup
    accDescr: Entering acquires the resource and the body runs. Whether the body finishes or raises, exit releases the resource; an exception not suppressed by exit then propagates.
    E[Enter: acquire] --> B[Run body]
    B -->|finishes| X[Exit: release]
    B -->|raises| X
    X --> N[Continue, or re-raise]

Use with for files, locks, database transactions, and temporary state, and write small ones with contextlib.contextmanager, for example a transaction that commits after yield and rolls back on error.4

A pathlib.Path names a location and is not an open resource; opening is a separate step that with pairs with closing. Iterate over an open file for large inputs rather than calling read_text(). Mode "w" replaces content and "a" appends.5

Modules and packages

A module is the namespace built from one .py file, and a package is built from a directory of modules. Importing runs the file once, which is why if __name__ == "__main__": separates “run directly” from “imported”. Use absolute imports, avoid from module import *, keep code under a src/ layout with pyproject.toml, and run package modules with python -m.6

Classes

DecoratorReceivesUse for
noneselfBehavior that reads or changes instance state
@classmethodclsAlternative constructors
@staticmethodnothingHelpers that need neither instance nor class

Use @dataclass for classes that mainly hold data, and prefer composition to inheritance unless there is a genuine “is-a” relationship.7

Type hints

Hints are checked by external tools, never by the interpreter. Accept the most abstract type a function needs, such as Iterable or Mapping, and return the most concrete type callers can rely on. Use object for an unknown value that must be narrowed, and avoid Any unless checking truly has to be bypassed.8

Tests

A test asserts observable behavior for a given input, so it survives a safe refactor and fails when behavior breaks. The standard library’s unittest is enough for a small suite: subclass TestCase, use assertEqual and assertRaises, and run python -m unittest discover. Cover a normal case, meaningful boundaries, and known regressions, never implementation details.9

Footnotes

  1. Python Iterators and Generators Cheatsheet, original ↩

  2. Python Decorators Cheatsheet, original ↩

  3. Python Exceptions Cheatsheet, original ↩

  4. Python Context Managers Cheatsheet, original ↩

  5. Python Files and Paths Cheatsheet, original ↩

  6. Python Modules and Packages Cheatsheet, original ↩

  7. Python Classes Cheatsheet, original ↩

  8. Python Type Hints Cheatsheet, original ↩

  9. Python Testing Cheatsheet, original ↩