What you'll learn
- Write docstrings people actually read: purpose, parameters, return, raises, and an example
- Keep examples honest with
doctest, which runs them as tests - Use type hints as documentation and let tools render them
- Generate a documentation site from docstrings with
pdocor MkDocs, and write the README as the front door
Concept
Code is read far more often than it is written, and most of that reading is by someone deciding whether to call a function, not how it works. A docstring answers that reader in one place: what the function does, what it takes, what it returns, what it raises, and one example. Python attaches it to the object, help() prints it, editors show it on hover, and documentation generators turn it into a site. A function without a docstring forces every caller to read its body.

Keep to one convention across a project. Google style, Args:, Returns:, Raises:, Example:, is the most readable in plain text and what this course uses; NumPy and Sphinx styles exist and tools understand all three. The first line is a one-sentence summary in the imperative, "Parse a line into an Expense", ending in a period, followed by a blank line and the details. Document what a caller needs, not the implementation: behavior, edge cases, units, and errors. Do not repeat the types, because the type hints already say them; say what the values mean.
| Docstring part | Answers | Example line |
|---|---|---|
| summary line | what does it do? | Parse one CSV line into an Expense. |
Args: |
what do the parameters mean, in what units? | line: a CSV row with day, category, amount. |
Returns: |
what comes back, and when it is None? |
The parsed Expense. |
Raises: |
which errors, under what conditions? | ParseError: if a field is missing or invalid. |
Example: / >>> |
how is it called? | >>> parse_amount("€12.50") |
Examples rot unless something runs them. doctest scans docstrings for >>> lines, executes them, and compares the printed result with the text below; python -m doctest module.py or pytest --doctest-modules runs every example as a test. A docstring example that is also a test cannot lie. Keep examples short and deterministic, avoid anything with unstable output like dictionaries of floats or timestamps, and use # doctest: +ELLIPSIS when part of the output does not matter.
Type hints are documentation the tools verify, so a well-hinted signature with a good docstring is complete; pdoc renders both into an HTML site for the whole package with one command and no configuration, which is the right first documentation site for a library. MkDocs with the mkdocstrings plugin is the step up when you want prose pages alongside the API reference. Above all sits the README: what the project does in one paragraph, how to install it, one example, and where to go next. It is the page everyone reads and most of them stop at, so write it first and keep it current.

Code
A module with complete docstrings in Google style, including runnable examples. help() shows them exactly as written.
# money.py
"""Money helpers: parsing amounts and splitting bills.
All amounts are in euros unless stated otherwise.
"""
def parse_amount(text: str) -> float:
"""Parse a money string like ``"€12.50"`` or ``"12,50"`` into euros.
Accepts an optional leading euro sign and either ``.`` or ``,`` as the
decimal separator. Whitespace around the value is ignored.
Args:
text: The raw amount as typed by a user or read from a file.
Returns:
The amount rounded to cents.
Raises:
ValueError: If ``text`` is empty, negative, or not a number.
Example:
>>> parse_amount("€12.50")
12.5
>>> parse_amount(" 7,25 ")
7.25
"""
cleaned = text.strip().removeprefix("€")
if "," in cleaned and "." not in cleaned:
cleaned = cleaned.replace(",", ".")
if not cleaned:
raise ValueError("empty amount")
value = round(float(cleaned), 2)
if value < 0:
raise ValueError("amount must not be negative")
return value
def split_evenly(total: float, people: int) -> list[float]:
"""Split ``total`` across ``people``, distributing leftover cents to the first shares.
Args:
total: The amount to split, in euros.
people: How many shares; must be at least 1.
Returns:
One share per person, summing exactly to ``total``.
Raises:
ValueError: If ``people`` is less than 1.
Example:
>>> split_evenly(10.0, 3)
[3.34, 3.33, 3.33]
"""
if people < 1:
raise ValueError("people must be at least 1")
cents = round(total * 100)
base, extra = divmod(cents, people)
return [(base + (1 if i < extra else 0)) / 100 for i in range(people)]
if __name__ == "__main__":
help(parse_amount)
Help on function parse_amount in module __main__:
parse_amount(text: str) -> float
Parse a money string like ``"€12.50"`` or ``"12,50"`` into euros.
Accepts an optional leading euro sign and either ``.`` or ``,`` as the
decimal separator. Whitespace around the value is ignored.
Args:
text: The raw amount as typed by a user or read from a file.
Returns:
The amount rounded to cents.
Raises:
ValueError: If ``text`` is empty, negative, or not a number.
Example:
>>> parse_amount("€12.50")
12.5
>>> parse_amount(" 7,25 ")
7.25
Running the examples as tests. Both docstrings pass; then a deliberately wrong example is added to a copy to show what a failing doctest reports.
# run_doctest.sh
python -m doctest -v money.py | tail -3
sed 's/\[3.34, 3.33, 3.33\]/[3.33, 3.33, 3.34]/' money.py > money_wrong.py
python -m doctest money_wrong.py | grep -E "Expected|Got|^\s+\[" | head -4
3 tests in 3 items.
3 passed.
Test passed.
Expected:
[3.33, 3.33, 3.34]
Got:
[3.34, 3.33, 3.33]Docstrings and hints read back programmatically, which is what every documentation tool does: inspect gives the signature, __doc__ the text, and the annotations the types.
# introspect.py
import inspect
import money
for name, obj in inspect.getmembers(money, inspect.isfunction):
sig = inspect.signature(obj)
summary = inspect.getdoc(obj).splitlines()[0]
print(f"{name}{sig}")
print(f" {summary}")
print(f" raises: {[ln.strip().split(':')[0] for ln in inspect.getdoc(obj).splitlines() if ln.strip().endswith(('.',)) and 'Error' in ln][0]}")
print(money.__doc__.splitlines()[0])
parse_amount(text: str) -> float
Parse a money string like ``"€12.50"`` or ``"12,50"`` into euros.
raises: ValueError
split_evenly(total: float, people: int) -> list[float]
Split ``total`` across ``people``, distributing leftover cents to the first shares.
raises: ValueError
Money helpers: parsing amounts and splitting bills.Generating a site with pdoc and writing the README that points to it. pdoc is installed only for this block; the README is the front door and needs only four things.
# site_and_readme.sh
uv run --with pdoc python -m pdoc money -o site >/dev/null 2>&1
ls site
grep -c "parse_amount" site/money.html | xargs -I{} echo "parse_amount mentioned {} times in the generated page"
cat > README.md <<'MD'
# money
Parse money strings and split bills without losing cents.
## Install
pip install money-helpers
## Example
>>> from money import parse_amount, split_evenly
>>> split_evenly(parse_amount("€10"), 3)
[3.34, 3.33, 3.33]
## More
API reference: https://example.com/money/ · Changelog: CHANGELOG.md · Issues: GitHub
MD
grep -E "^#|^ >>>" README.md
index.html
money.html
search.js
parse_amount mentioned 42 times in the generated page
# money
## Install
## Example
>>> from money import parse_amount, split_evenly
>>> split_evenly(parse_amount("€10"), 3)
## MoreTry it yourself
Add Google-style docstrings with a runnable example to every public function and class in the expense library, then run pytest --doctest-modules src and fix any example that fails. Generate a site with pdoc src/expenses -o site and open site/index.html. Write the project README with the four sections from this lesson, and check that a stranger could install and run one command from it without reading anything else.
Common mistakes
- Docstrings that restate the signature. "Takes a string and returns a float" is what the hints say; explain meaning, units, edge cases, and errors.
- Examples nobody runs. Without
doctestor--doctest-modules, examples drift from the code and mislead; run them in CI. - Documenting everything except the README. The README is the page people actually read; write it first and keep install and example current.
What's next
- A docstring gives the summary,
Args,Returns,Raises, and an example;help(), editors, and generators all read it. doctestruns the examples as tests so they cannot lie; type hints are documentation tools verify.pdocgenerates a reference site from docstrings and hints; the README is the front door: what, install, example, links.
Your code explains itself. Next you will keep its settings and secrets out of the code entirely: configuration from the environment and TOML.