A docstring earns its place by saying what type hints cannot: meaning, constraints,
side effects, and failure conditions. reST style expresses those in Sphinx's native
language — info field lists (:param:, :returns:, :raises:) and cross-reference
roles — so write for two readers at once: a human scanning a tooltip and Sphinx
rendering API docs with real links.
Canonical shape
async def fetch_one(self, query: str, *args: Any) -> Mapping[str, Any] | None:
"""Execute a query and return the first matching row.
:param query: SQL query text with numbered placeholders.
:param args: Positional parameters bound to the placeholders.
:returns: The first row as a mapping, or ``None`` when no rows match.
:raises QueryError: If the query is malformed or the connection is closed.
"""
- Field order: `:param:` → `:returns:` → `:raises:`; keep the field list as one
block after the summary (and optional body), separated by a blank line.
- Name varargs without stars in the field (`:param args:`); the signature already
shows the stars.
- With annotations present, omit `:type:` and `:rtype:` — duplicating annotations
invites drift, and Sphinx autodoc can merge annotations into the output.
## Summary line
- One line directly after `"""`, ending with a period; blank line before anything
else. State what the caller gets — never "This function ...".
- PEP 257 prescribes imperative mood for functions and methods: a command such as
"Return the first row.", not the description "Returns the first row.".
## Field lists
| Field | Use for |
|---|---|
| `:param name:` | Parameter description (aliases: `parameter`, `arg`, `argument`) |
| `:param type name:` | Inline type — works only when the type is a single word |
| `:type name:` | Parameter type, when needed and not annotated |
| `:returns:` | Meaning of the return value (alias: `return`) |
| `:rtype:` | Return type, when needed and not annotated |
| `:raises ExcName:` | Trigger condition (aliases: `raise`, `except`, `exception`) |
| `:ivar name:` / `:cvar name:` | Instance/class variables — prefer trailing docstrings instead |
Standardize on `:param:`, `:returns:`, and `:raises:` — the aliases render the same,
but one spelling keeps grep and review trivial. Sphinx cross-references the exception
name in `:raises ExcName:` automatically, like an implicit `:exc:` role. Include only
fields that add information; a restating field is noise.
## Cross-references and literals
Use roles instead of plain names — they become real links in rendered docs:
`:mod:` (modules), `:class:` (classes), `:meth:` (methods), `:func:` (functions),
`:attr:` (attributes and properties), `:exc:` (exceptions), `:data:` (module-level
data), `:obj:` (anything else). Prefix a target with `~` to render only its last
component:
```text
:meth:`initialize` -> link "initialize" (resolved within the class)
:class:`pkg.mod.Foo` -> link "pkg.mod.Foo" (cross-module: fully qualify)
:meth:`~queue.Queue.get` -> link rendered as just "get"
Unqualified targets resolve against the current class, then the current module —
so short names work locally; fully qualify cross-module targets. Use double
backticks for literal values — None, 'tuple', SQL fragments, flags,
environment variables — so they render as code, not prose.
:raises: discipline
- Document only exceptions relevant to the caller's interface, each with its trigger condition — a bare exception name tells the caller nothing actionable.
- Never document exceptions raised because the caller violated the documented contract: that would paradoxically make behavior under violation of the API part of the API.
- On protocols and other interfaces, add
:raises:only when raising is a required part of the contract, not a detail of one implementation.
Beyond plain functions
Classes and their attributes, properties, generators, type aliases, constants,
TypedDict, and @overload / Protocol stubs each have a shape of their own —
references/constructs.md. The rule that governs all
of them: document the contract at the level the caller meets it. A property
is documented as the value it yields, not as the method it happens to be; an
@overload stub documents the one signature it declares, and the implementation
carries the shared body.
Length and formatting
- Default docstring is the summary line alone. Add a body only when it earns its
place — non-obvious behavior, side effects, invariants, "why" — in 1–3 sentences.
Size
:param:/:returns:/:raises:to the real API surface. - Never restate the type in prose. Annotations carry the type; the description adds semantics: units, constraints, defaults on absence.
- Blank line between the summary (or body) and the field list.
- Wrap near 88 columns; the summary must stay on one physical line.
Anti-patterns
| Wrong | Right |
|---|---|
:param timeout: The timeout, an integer. |
:param timeout: Seconds to wait for a pool connection. |
:returns: The result. |
:returns: True if the row was inserted. |
"""Returns the pathname.""" |
"""Return the pathname.""" — PEP 257 imperative mood |
Plain-text reference PostgresClient.transaction |
:meth: role — plain names never link |
:yields: field |
Describe the iterator in :returns: — Sphinx has no yields field |
:raises ValueError: for arguments the contract already forbids |
Omit — contract violations are not interface behavior |
Related skills
- python-google-docstrings — the same rules expressed as Google-style sections via Sphinx Napoleon
readable-code— better names and structure shrink what a docstring must explain. Absent it, the boundary still holds: a docstring states the contract, not the implementation.- altitude-docs — deciding what belongs in docstrings vs higher-level documentation