Documentation

Good documentation — docstrings, generated API references, and Markdown guides — is what lets other people (and future you) use code correctly without reading its source.

Documentation is the layer between what code does and what a human needs to know to use it correctly. Python treats one form of documentation, the docstring, as a first-class language feature accessible at runtime via __doc__ and help(); everything built on top of docstrings — generated API references, project guides, published websites — is third-party or ecosystem tooling layered on that foundation.

Why Documentation Matters

Code answers "how," documentation answers "why" and "how do I use this." A function's implementation shows exactly what it does line by line, but a caller usually needs a higher-level summary: what arguments it expects, what it returns, what can go wrong, and why it exists at all — none of which is reliably inferable from reading the body alone, especially for non-trivial logic.

Documentation also has an audience split worth keeping distinct: docstrings and API references serve people *calling* your code; README/guide-style Markdown docs serve people trying to understand, install, or contribute to the *project* as a whole. Both matter, and conflating them (a wall of prose inside a docstring, or an API reference crammed into a README) tends to serve neither audience well.

Docstrings Basics (PEP 257)

A docstring is a string literal that appears as the first statement in a module, function, class, or method. Python stores it on the object's __doc__ attribute automatically — no decorator or import required — and help() displays it. PEP 257 defines the conventions: triple double-quotes, a summary line, and for anything beyond a one-liner, a blank line before further detail.

A docstring is a real string object evaluated at definition time (unlike a # comment, which the parser discards) — this is exactly why tools can introspect it at runtime to build documentation automatically.

def slugify(text: str) -> str:
    """Convert text to a lowercase, hyphen-separated slug."""
    return text.strip().lower().replace(" ", "-")

print(slugify.__doc__)
# Convert text to a lowercase, hyphen-separated slug.

help(slugify)
# Help on function slugify in module __main__:
#
# slugify(text: str) -> str
#     Convert text to a lowercase, hyphen-separated slug.

Docstring Styles

PEP 257 sets baseline conventions but doesn't prescribe a structured format for parameters/returns/raises — that's filled in by community conventions. Three are widely used; pick one per project and apply it consistently, since mixing styles defeats the tooling that parses them.

# Google style
def divide(a: float, b: float) -> float:
    """Divide a by b.

    Args:
        a: The numerator.
        b: The denominator. Must be nonzero.

    Returns:
        The quotient a / b.

    Raises:
        ZeroDivisionError: If b is zero.
    """
    return a / b


# NumPy style
def divide(a: float, b: float) -> float:
    """Divide a by b.

    Parameters
    ----------
    a : float
        The numerator.
    b : float
        The denominator. Must be nonzero.

    Returns
    -------
    float
        The quotient a / b.
    """
    return a / b


# reStructuredText (reST) style -- what Sphinx's default parser expects
def divide(a: float, b: float) -> float:
    """Divide a by b.

    :param a: The numerator.
    :param b: The denominator. Must be nonzero.
    :returns: The quotient a / b.
    :raises ZeroDivisionError: If b is zero.
    """
    return a / b

Module, Class, and Function-Level Documentation

Docstrings apply at three levels, each with a different job. A module docstring (first statement in the .py file) explains what the module is for as a whole. A class docstring explains the concept the class represents and its overall usage, while __init__'s own docstring (or the class docstring, by convention) documents constructor parameters. A function/method docstring documents that one callable's contract.

"""Utilities for parsing and validating shipping addresses."""

class Address:
    """A validated postal address.

    Instances are immutable once constructed; use `Address.parse`
    to build one from raw user input.
    """

    def __init__(self, street: str, city: str, postal_code: str) -> None:
        self.street = street
        self.city = city
        self.postal_code = postal_code

    def format_label(self) -> str:
        """Return a shipping-label-formatted multi-line string."""
        return f"{self.street}\n{self.city} {self.postal_code}"

Sphinx (third-party)

Sphinx is the dominant third-party documentation generator in the Python ecosystem — it powers the official Python docs themselves, as well as most major library documentation sites. It builds HTML (and other formats) from reStructuredText or Markdown source files, and its autodoc extension can pull docstrings directly out of your code to generate API reference pages automatically, so the reference docs stay in sync with the code as long as docstrings are kept current.

Sphinx is not part of the standard library; installing and configuring it (typically via pip install sphinx plus a conf.py) is a project-level choice.

# docs/api.rst -- with Sphinx's autodoc extension enabled
.. automodule:: myapp.orders
   :members:
   :undoc-members:
   :show-inheritance:

$ pip install sphinx
$ sphinx-build -b html docs/ docs/_build/

Markdown for Project Docs

Markdown is a lightweight plain-text markup format, ubiquitous for README files, contribution guides, and changelogs because it renders readably both as raw text and as formatted HTML on platforms like GitHub. It's the natural choice for project-level documentation (installation, quickstart, architecture overview) as opposed to API reference, which is usually better generated from docstrings so it can't drift out of sync with the code.

# README.md
## Installation

    pip install myapp

## Quickstart

```python
from myapp import Client
client = Client(api_key="...")
client.orders.list()
```

See the [full API reference](https://myapp.readthedocs.io) for details.

API Documentation Generation

"API documentation" specifically means a reference describing every public function, class, and method — usually generated rather than hand-written, because hand-written reference docs go stale the moment a signature changes. Sphinx's autodoc is one path; other third-party tools (mkdocs with the mkdocstrings plugin, pdoc) take a similar approach: introspect the code (via docstrings and type hints) and render structured reference pages, often hosted automatically (e.g. Read the Docs, a third-party hosting service that rebuilds docs on every commit).

Type hints (see the Type Hints topic) double as documentation input: a generated API reference can show parameter and return types directly from annotations, without the author retyping them in prose.

Writing Good Examples

A runnable example is often more useful than a paragraph of prose — it shows real usage, and (if kept as a doctest or tested snippet) can be verified to still work as the code evolves. The standard library's own doctest module can execute >>> examples embedded in docstrings and fail if the shown output no longer matches, turning documentation into a lightweight regression check.

These >>> lines are runnable via python -m doctest mymodule.py (standard library) -- if a future change breaks clamp's behavior, the doctest fails and the drift is caught.

def clamp(value: float, low: float, high: float) -> float:
    """Clamp value to the inclusive range [low, high].

    >>> clamp(15, 0, 10)
    10
    >>> clamp(-5, 0, 10)
    0
    >>> clamp(5, 0, 10)
    5
    """
    return max(low, min(value, high))

Documentation Quality Principles

A few principles separate documentation that helps from documentation that's ignored: describe *why* something exists or *when* to use it, not just a restatement of the function name; document edge cases and failure modes (what raises, what happens on empty input), not just the happy path; keep docs next to the code they describe so they're updated in the same commit as behavior changes; and prefer fewer, accurate docs over exhaustive but stale ones — outdated documentation actively misleads and is often worse than none.

# low value: just restates the name
def get_user(id):
    """Get a user."""
    ...

# higher value: explains the contract and failure mode
def get_user(user_id: int) -> User:
    """Look up a user by primary key.

    Raises UserNotFoundError if no user with this id exists --
    callers that want a None fallback should use get_user_or_none.
    """
    ...

Complete Example

Module docstring for orientation, class docstring for the data model, and a Google-style function docstring with a doctest-verifiable example -- three documentation levels working together.

"""Order pricing utilities.

This module computes order totals including tax, given a list of
line items. See the Discounts module for promotional pricing.
"""

from __future__ import annotations

from dataclasses import dataclass

TAX_RATE = 0.08


@dataclass
class LineItem:
    """A single priced item within an order."""
    name: str
    unit_price: float
    quantity: int = 1


def calculate_total(items: list[LineItem], *, apply_tax: bool = True) -> float:
    """Return the total price for a list of line items.

    Args:
        items: The line items to total.
        apply_tax: Whether to add TAX_RATE on top of the subtotal.

    Returns:
        The order total, taxed if apply_tax is True.

    Examples:
        >>> calculate_total([LineItem("Widget", 10.0, 2)], apply_tax=False)
        20.0
    """
    subtotal = sum(item.unit_price * item.quantity for item in items)
    return subtotal * (1 + TAX_RATE) if apply_tax else subtotal

Quick Reference

Layer               Lives in           Purpose
-------------------  -----------------  -----------------------------------
Docstring            source code        per-object contract, runtime __doc__
Docstring style       (project choice)  Google / NumPy / reST -- pick one
Generated API ref     Sphinx/mkdocs+plugins (third-party) -- from docstrings
Project guide         README.md, docs/  Markdown -- install, quickstart, why
Runnable example       >>> in docstring stdlib doctest can verify it stays true

Key Takeaways

A docstring is a real Python object (__doc__), evaluated at definition time — unlike a # comment, tools can introspect it at runtime.

PEP 257 sets baseline docstring conventions; Google/NumPy/reST styles fill in structure for parameters, returns, and exceptions — pick one per project.

Sphinx (and alternatives like mkdocstrings, pdoc) are third-party tools that generate API reference documentation from docstrings and type hints automatically.

Markdown serves project-level docs (README, guides); generated docstring-based references serve API-level docs — keep the two distinct.

Document the *why*, the contract, and the failure modes, not just a restatement of the name — and keep docs close to the code so they change together.

def get_total(items): """Get total.""" return sum(i.price for i in items) def get_total(items: list[LineItem]) -> float: """Sum the price of every line item. Returns 0.0 for an empty list. """ return sum(i.price for i in items)

A docstring that just repeats the function name adds no information a reader didn't already have; document behavior on edge cases (empty input) and, ideally, types.

Stale documentation actively misleads readers, often worse than no documentation at all -- keeping docs in the same commit as the code change, or generating them from source, prevents drift.

def parse(data): """Parses data. a is the data. returns parsed.""" def parse(data: bytes) -> ParsedRecord: """Parse a raw record. Raises ValueError if data is not valid record framing. """

A docstring using undefined placeholder names (a) that don't match the actual signature, and omitting failure modes, gives a caller false confidence about what the function actually does.

What makes a docstring different from a # comment, mechanically?

A docstring is a real string literal evaluated at definition time and stored on the object's __doc__ attribute, so it's introspectable at runtime (help(), documentation generators). A # comment is discarded by the parser and has no runtime representation.

Name the three widely used docstring styles and one place each is common.

Google style (readable, common in many open-source projects), NumPy style (common in the scientific Python ecosystem), and reStructuredText/reST style (Sphinx's default docstring parser expects this).

Is Sphinx part of the Python standard library?

No. Sphinx is a third-party documentation generator, installed separately (e.g. pip install sphinx); it powers the official Python docs but is not bundled with the interpreter.

Why prefer Markdown for a README but a generated docstring-based reference for API documentation?

A README serves project-level orientation (install, quickstart, why) best expressed as free-form prose; an API reference needs to stay exactly in sync with every function signature, which only generation from docstrings/type hints can guarantee as code evolves.

What does the standard library's doctest module do with >>> lines in a docstring?

It executes them as real Python code and compares the shown output to the actual output, failing if they diverge -- turning a documentation example into an automatically verified regression check.

Why is a docstring that only restates the function's name considered low quality?

It adds no information the reader didn't already infer from the name itself; useful docstrings explain the contract, edge cases, and failure modes -- information not visible from the signature alone.

Where should a module-level docstring appear in a .py file?

As the very first statement in the file (before imports), so it's what help(module) and documentation generators surface as the module's top-level description.

Write a Google-style docstring for a function def retry(fn, times=3) that retries a callable up to times times, raising the last exception if all attempts fail.

def retry(fn, times: int = 3): """Call fn, retrying on exception up to times attempts. Args: fn: A zero-argument callable to invoke. times: Maximum number of attempts before giving up. Returns: The return value of the first successful call. Raises: Exception: The exception from the final failed attempt, if every attempt raises. """

The docstring documents the contract (retries, then re-raises) rather than restating the name, and covers the failure mode explicitly under Raises.

Running python -m doctest on this module fails. Why, and what's the likely fix?

math.pi * 2**2 is approximately 12.566370614359172, not the rounded value 12.56 shown in the doctest -- doctest compares exact printed output. The fix is either to round in the example call (round(area(2), 2)) or show the full unrounded value in the expected output.

Write a one-line module docstring and a class docstring for a class RateLimiter that limits calls to N per second, distinguishing what each docstring is responsible for.

"""Rate limiting utilities for outbound API calls.""" class RateLimiter: """Limits calls to at most rate per second, blocking as needed."""

The module docstring orients a reader to the module's overall purpose (a whole file of rate-limiting utilities); the class docstring documents the specific behavioral contract of RateLimiter itself -- each answers a different-scoped question.

def area(radius):
    """
    >>> area(2)
    12.56
    """
    import math
    return math.pi * radius ** 2