Documenting Python Code
Documenting Python Code involves using comments, docstrings, and tools to explain code purpose, functionality, and usage for better readability and collaboration.
Documenting Python code is fundamentally about communicating intent, contracts, usage, assumptions, externally meaningful behavior, and non-obvious implementation reasoning. This communication occurs through comments, docstrings, interface documentation, examples, runtime help, and documentation-generation mechanisms, each serving distinct but complementary roles in helping users and maintainers understand and work effectively with Python code.
Foundations of Documenting Python Code
Documentation is information that helps readers understand how and why Python code should be used or maintained. It is distinct from the executable behavior itself, which is defined by the code’s syntax and semantics. Documentation supplements code by clarifying intent, constraints, and usage that are not immediately obvious from the code alone.
Useful documentation emphasizes intent, public behavior, assumptions, invariants, limitations, side effects, and rationale. It avoids mechanically restating syntax or operations already clear from the code. Instead, it provides insights into why code exists and how it should be used or modified safely.
Different documentation forms answer different questions:
- Comments explain local reasoning, assumptions, or subtle details within the code.
- Docstrings describe interfaces by capturing purpose, parameters, return values, and usage contracts.
- Type annotations specify data types but do not fully describe semantics.
- Examples demonstrate typical interface usage concretely.
- External prose documentation provides conceptual overviews, tutorials, and rationale.
- Generated reference material compiles structured interface information for browsing or lookup.
| Documentation Form | Audience | Location | Primary Responsibility | Relationship to Executable Code |
|---|---|---|---|---|
| Comments | Code maintainers | Inside source code | Explain non-obvious details, rationale | Inline, ignored by interpreter |
| Docstrings | Users and maintainers | First statement in modules, classes, functions | Document interface contracts and usage | Accessible at runtime via __doc__ |
| Interface Documentation | Interface users | Modules, packages, classes, functions | Describe public API purpose and constraints | Derived from docstrings and source |
| Examples | Interface users, learners | Docstrings, external docs | Show representative usage and expected results | Illustrative, runnable or illustrative |
| Runtime Help | Interactive users | REPL, consoles | Provide on-demand access to docstrings | Uses docstrings and introspection |
| Generated Documentation | Developers, users | HTML, PDF, websites | Present structured reference and navigation | Based on source docstrings and metadata |
A clear conceptual illustration:
Documentation maintenance is an integral part of code maintenance. Outdated documentation can be worse than no documentation because readers may treat it as authoritative and be misled. Keeping documentation synchronized with code changes ensures it remains trustworthy and effective.
Python Code Comments
Python comments are source text beginning with # outside string literals. They communicate information only to human readers and do not become part of runtime expression evaluation.
Block comments are groups of comment lines associated with a following logical section of code. They describe purpose, reasoning, assumptions, or constraints rather than mechanically translating each statement into English.
Example:
# Increment the counter for each valid user
counter += 1 # This comment merely repeats the code
# The counter tracks the number of active users who passed validation,
# including those with exceptions for special roles.
# It must never exceed the maximum allowed concurrent sessions.
counter += 1 # Non-obvious invariant explained here
Inline comments are brief annotations following code. Use them sparingly when a localized non-obvious fact cannot be expressed more clearly by naming or code structure.
Concise examples:
Useful inline comment:
result = compute_discount(price) # price in USD, excludes taxes
Unnecessary inline comment:
x = x + 1 # add one to x
Over-commented example rewritten:
# Over-commented
# Calculate the sum of x and y
sum = x + y # add x and y
# Improved: descriptive naming and structure
def add(x, y):
return x + y
sum = add(x, y)
Comments recording rationale, compatibility constraints, deliberate deviations, units, external assumptions, or subtle algorithmic properties remain valuable even when implementations change.
Maintenance markers like TODO or FIXME should be actionable and include sufficient context to identify the unresolved issue rather than vague reminders.
Avoid commented-out obsolete code. Prefer removal or use version-control history for reference.
Poorly commented function:
def calc(a, b):
# check if a is positive
if a > 0:
# do division
return a / b # returns result
else:
# return zero if not positive
return 0
Revised:
def divide_if_positive(a, b):
# Returns a / b if a > 0; otherwise returns 0.
# Assumes b != 0.
if a > 0:
return a / b
return 0
Python Docstrings
A docstring is a string literal recognized as documentation when it appears as the first statement in a module, class, function, or method body. Unlike arbitrary string literals elsewhere, recognized docstrings are accessible via the documented object's __doc__ attribute.
Example with docstrings:
"""Module-level docstring describing module purpose."""
class Example:
"""This class represents an example abstraction."""
def method(self):
"""Perform an action and return True on success."""
return True
def func(x):
"""Compute the square of x."""
return x * x
print(__doc__) # Module docstring
print(Example.__doc__) # Class docstring
print(Example.method.__doc__) # Method docstring
print(func.__doc__) # Function docstring
One-line docstrings provide concise summaries suitable for simple interfaces and should describe purpose or behavior rather than repeat names or signatures.
Multi-line docstrings start with a summary, followed by a blank line, then detailed explanation covering behavior, parameters, return values, exceptions, side effects, invariants, or usage notes—only when these materially help users.
Well-documented function example:
def calculate_area(radius):
"""
Calculate the area of a circle given its radius.
Parameters:
radius (float): Radius of the circle, must be non-negative.
Returns:
float: Area of the circle.
Raises:
ValueError: If radius is negative.
Notes:
Uses π approximated by math.pi.
"""
import math
if radius < 0:
raise ValueError("Radius cannot be negative")
return math.pi * radius ** 2
Public docstrings document behavior rather than implementation internals so refactoring internals does not unnecessarily invalidate the interface contract.
Only document exceptions callers need to understand—do not list every incidental internal exception.
Document mutation, persistent state changes, external effects, or resource ownership when relevant to safe usage.
Docstring formatting conventions vary; semantic completeness and consistency matter more than mixing markup styles arbitrarily.
Raw docstrings (prefixing with r) prevent backslash escapes from being interpreted but do not change conceptual content.
Python Interface Documentation
Interface documentation focuses on what users of modules, packages, classes, functions, methods, and scripts need to use them correctly without depending on private implementation details.
Module and Package Documentation in Python
Document modules by explaining their purpose, principal public capabilities, important module-level assumptions, representative usage, and relationships among public interfaces.
Example module:
"""
math_utils.py
Provides mathematical utilities for geometric calculations.
"""
PI = 3.141592653589793
class Circle:
"""Represents a circle with a given radius."""
def __init__(self, radius):
"""Initialize circle with radius."""
self.radius = radius
def area(self):
"""Return the area of the circle."""
return PI * self.radius ** 2
def square(x):
"""Return the square of x."""
return x * x
Package-level documentation communicates the package's conceptual purpose, major public entry points, initialization behavior if relevant, and how users should approach the public interface, without cataloging every internal module.
Class Documentation in Python
Document the abstraction represented by a class, construction expectations, important invariants, significant public attributes, and behavioral responsibilities.
Example class with documentation:
class Rectangle:
"""
Represents a rectangle defined by width and height.
Invariants:
width > 0
height > 0
Public attributes:
width (float): Width of the rectangle.
height (float): Height of the rectangle.
"""
def __init__(self, width, height):
"""Initialize a rectangle with width and height."""
self.width = width
self.height = height
def area(self):
"""Return the area of the rectangle."""
return self.width * self.height
Document public attributes when names and annotations alone do not fully convey their meaning, units, mutability, or lifecycle.
Function and Method Documentation in Python
Document operation purpose, parameter semantics, return meaning, exceptions, side effects, preconditions, postconditions, and usage examples according to interface complexity.
| Documentation Concern | When Useful |
|---|---|
| Purpose | Always, to explain what the function does |
| Parameters | When semantics, constraints, or units matter |
| Return Value | When meaning or special cases affect use |
| Exceptions | When callers need to handle specific failures |
| Side Effects | When mutations or external effects occur |
| Preconditions | When callers must satisfy conditions |
| State Changes | When persistent or global state is affected |
| Examples | To illustrate typical or boundary usage |
Parameter documentation adds semantic information such as meaning, accepted forms, units, or constraints beyond names and type annotations.
Document return values through their semantic meaning and special cases like None or sentinels with distinct caller interpretation.
Contrasting docstrings:
def multiply(x, y):
"""Multiply x and y."""
return x * y
def multiply(x, y):
"""
Multiply two numbers.
Parameters:
x (int or float): First factor.
y (int or float): Second factor.
Returns:
int or float: Product of x and y.
"""
return x * y
Script Documentation in Python
Document directly executable Python programs with purpose, invocation expectations, input/output behavior, side effects, exit behavior, configuration assumptions, and representative command usage. Avoid turning comments into full manuals.
Example script docstring:
"""
process_data.py
Process input data files to generate summarized reports.
Usage:
python process_data.py input.csv output.txt
Arguments:
input.csv CSV file containing raw data
output.txt File to write summary results
Behavior:
Reads input, filters invalid rows, computes aggregates,
and writes textual report to output.
Exit codes:
0 on success, non-zero on failure.
"""
Interface documentation describes externally meaningful behavior, while implementation comments explain local reasoning that maintainers need.
Examples in Python Documentation
Documentation examples concretely demonstrate intended interface use, complementing abstract descriptions by showing representative inputs, operations, and observable results.
Select examples that first demonstrate common successful use, then important boundary, configuration, or failure behavior only if they materially improve understanding.
Example interface with usage examples:
def factorial(n):
"""
Compute factorial of n (n!).
Examples:
>>> factorial(5)
120
>>> factorial(0)
1
"""
if n == 0:
return 1
return n * factorial(n - 1)
Examples serve as executable-looking contracts using valid contemporary Python syntax, realistic values, and outputs consistent with documented behavior.
Interactive-style examples using >>> prompts suit human reading and may be verified by doctest tools but are distinct from comprehensive testing.
Example with interactive-style examples:
def greet(name):
"""
Return a greeting message.
Examples:
>>> greet('Alice')
'Hello, Alice!'
"""
return f"Hello, {name}!"
Examples involving randomness, time, unordered output, environment-specific paths, concurrency, or external state require deliberate stabilization or abstraction so documentation does not imply one accidental result is universally guaranteed.
Avoid unnecessary setup, huge datasets, incidental framework details, and unexplained magic values that obscure the interface concept.
Keep examples synchronized with interface evolution; obsolete names, stale outputs, removed parameters, or changed semantics are documentation defects, not harmless drift.
Python Online Help and Documentation Generation
Python online help provides runtime access to documentation metadata for modules, classes, functions, methods, and other objects, principally deriving useful descriptions from names, signatures, docstrings, and object structure.
The built-in help() function offers an interactive interface displaying documentation about Python objects. Its usefulness depends on the quality of docstrings and introspection information exposed.
Representative interactive uses of help():
>>> import math
>>> help(math)
Help on module math:
NAME
math
DESCRIPTION
This module provides access to the mathematical functions
defined by the C standard.
FUNCTIONS
...
>>> help(math.sqrt)
Help on built-in function sqrt in module math:
sqrt(x, /)
Return the square root of x.
Access to raw docstrings via __doc__ differs from normalized retrieval through inspect.getdoc, which cleans indentation and formatting for display.
Example comparing __doc__ and inspect.getdoc:
import inspect
def example():
"""
This is a multi-line docstring.
It has indentation and extra spaces.
"""
pass
print("Raw __doc__:")
print(example.__doc__)
print("\nNormalized inspect.getdoc:")
print(inspect.getdoc(example))
The standard pydoc module locates Python objects and renders documentation derived from runtime interfaces and docstrings.
Representative pydoc commands:
$ python -m pydoc math
$ python -m pydoc math.sqrt
$ python -m pydoc -w math # Write HTML documentation for math module
Programmatic example using pydoc:
import pydoc
print(pydoc.render_doc(str))
Documentation generation transforms structured interface information—signatures, names, docstrings, and optional markup—into reference-oriented output. Generated presentation is distinct from the quality of the underlying documentation.
Generating reference documentation from maintained source interfaces reduces duplication and keeps signatures near implementation. Conceptual guides, tutorials, rationale, and architecture explanations often require separately authored prose.
Limitations of automatic generation include propagation of stale docstrings, exposure of unintended interfaces, poor organization, missing conceptual context, and mechanically complete but unhelpful reference material.
| Tool / Attribute | Input Object | Normalization | Presentation Purpose | Programmatic Use | Representative Audience |
|---|---|---|---|---|---|
__doc__ | Object's raw docstring | None | Raw docstring text | Direct access to stored string | Developers inspecting source |
inspect.getdoc | Object | Trim and clean layout | Cleaned docstring for display | Display-friendly docstring | Developers, runtime help |
help() | Object | Uses inspect.getdoc | Interactive help in REPL | Interactive use | Interactive users |
pydoc | Object or name string | Uses inspect.getdoc | Formatted reference documentation | Programmatic or CLI | Developers, users, documentation consumers |
Solved Python Documentation Exercise
Initial version (weak names, redundant comments, missing public docstrings, undocumented side effects, no usage example):
# This module does some math operations
def f1(x, y):
# add x and y
return x + y # returns sum
def f2(x):
# multiply x by 2
return x * 2
class C:
# class for math ops
def m1(self, val):
# subtract 1
return val - 1
Documented version:
"""
math_ops.py
Provides simple mathematical operations for demonstration purposes.
This module includes addition, doubling, and decrement operations
with clear contracts and usage examples.
"""
def add(x, y):
"""
Return the sum of x and y.
Parameters:
x (number): First addend.
y (number): Second addend.
Returns:
number: Sum of x and y.
Examples:
>>> add(2, 3)
5
"""
return x + y
def double(x):
"""
Return twice the value of x.
Parameters:
x (number): Input value.
Returns:
number: Two times x.
Examples:
>>> double(4)
8
"""
return x * 2
class Calculator:
"""
Calculator for simple arithmetic operations.
Methods:
decrement(val): Return val minus one.
"""
def decrement(self, val):
"""
Return val decreased by one.
Parameters:
val (number): Value to decrement.
Returns:
number: val minus one.
Notes:
This method does not mutate any state.
"""
return val - 1
Explanation of improvements:
- Removed redundant comments that restated obvious operations.
- Added a module docstring describing purpose and contents.
- Renamed functions and class for clarity and to express intent.
- Added docstrings to functions and methods to specify parameters, return values, and examples.
- Preserved rationale by noting that
decrementdoes not mutate state. - Included usage examples to demonstrate typical calls.
- Structured documentation supports runtime help and generated documentation.
Verification code for documentation retrieval:
import math_ops
import inspect
import pydoc
print("Module __doc__:")
print(math_ops.__doc__)
print("\nFunction add __doc__:")
print(math_ops.add.__doc__)
print("\nNormalized docstring via inspect.getdoc for add:")
print(inspect.getdoc(math_ops.add))
print("\nHelp for Calculator class:")
help(math_ops.Calculator)
print("\nPydoc for double function:")
print(pydoc.render_doc(math_ops.double))
Reviewing Python documentation involves checking for:
- Missing purpose statements.
- Duplicated or trivial syntax restatement.
- Unexplained parameters or special return cases.
- Stale or misleading examples.
- Undocumented side effects or state changes.
- Misleading comments or implementation leakage.
- Inconsistent terminology or naming.
- Documentation that no longer matches executable behavior.
Documentation density should match conceptual and interface complexity. Simple code requires minimal prose; subtle public behavior, state transitions, constraints, or external effects justify more detailed documentation.
Terminology consistency is important: stable names should be used for identical concepts to avoid reader confusion.
Public documentation must describe supported behavior and not promise incidental implementation details that may legitimately change without breaking the interface contract.