Documenting Python code means writing explanations that help other developers, and your future self, understand what the code does and why it was written that way. Python provides three built-in tools for this: docstrings, comments, and type hints. Each serves a different purpose.
Good documentation lives close to the code. When you change a function's behavior, you update its docstring in the same commit. Outdated documentation is worse than no documentation because it actively misleads the reader.
The habits in this article build on the formatting rules from the PEP 8 style guide. Here is a well-formed function docstring following the standard conventions:
def connect_to_database(host, port, timeout=30):
"""Open a connection to the database server.
The connection uses TCP and supports automatic reconnection if the
server becomes unreachable during the session. Callers should close
the connection with the disconnect function when finished.
"""
connection = _create_socket(host, port)
connection.settimeout(timeout)
return connectionFor simple functions where the name already explains the purpose, a one-line docstring on the same line is acceptable. Reserve the multi-line format for functions where the behavior needs more context than the name and signature already provide:
def is_valid_email(address):
"""Return True if address contains an @ sign and a domain part."""
return "@" in address and "." in address.split("@")[-1]Class docstrings describe the class purpose and public behavior. Method docstrings follow the same rules as function docstrings. Module docstrings go at the top of the file and describe what the module provides.
Triple double-quotes are the convention, even for one-liners. Tools like Sphinx and pydoc expect them, and consistency across a codebase matters more than personal preference.
Comments: explain the reasoning, not the action
Comments start with # and are ignored by the interpreter. Their job is to explain decisions that are not obvious from the code itself. A comment should answer "why," not "what."
The worst comment restates the code. The best comment explains a constraint, a workaround, or a deliberate choice that the reader would otherwise question:
# Bad: restates what is already obvious
count = count + 1 # increment count
# Good: explains a non-obvious decision
count = count + 1 # compensate for the 0-based index in the external APIBlock comments go on their own line before the code they describe. Inline comments go on the same line and should be used sparingly. If a block of code needs an inline comment on every line, the code is probably too clever and should be rewritten for clarity.
Avoid commenting out old code and leaving it in the file. Version control remembers the history. Dead code creates noise and makes the reader wonder whether it was left intentionally.
def process_orders(orders):
# Only process paid orders that have at least one item.
# Unpaid orders are handled by the billing cron job.
valid = [o for o in orders if o.is_paid and o.items]
# Sort by priority (high to low) so urgent orders ship first.
valid.sort(key=lambda o: o.priority, reverse=True)
for order in valid:
ship(order)The function name and variable names already say what happens. The comments explain the filtering rule and the sorting rationale, which are the non-obvious decisions.
Type hints: document the expected types
Type hints were added in Python 3.5 and have become a standard way to document what types a function expects and returns. They are optional annotations that tools like mypy and your editor can check, but Python does not enforce them at runtime.
A function with type hints says more about its contract than one without. The reader knows at a glance what type each parameter should be and what type the function returns:
from datetime import datetime
def calculate_age(birth_date: datetime, reference_date: datetime | None = None) -> int:
"""Return the age in whole years between birth_date and reference_date.
If reference_date is not provided, the current date is used.
"""
if reference_date is None:
reference_date = datetime.now()
return reference_date.year - birth_date.yearFor collections, use the generic types from the typing module. These tell the reader what the collection contains, not just that it is a list or dict:
from typing import Sequence
def find_active_users(users: Sequence[dict]) -> list[str]:
"""Return the names of users whose active field is truthy."""
return [u["name"] for u in users if u.get("active")]Type hints are documentation first, static checking second. Even if you never run mypy, the annotations help readers understand the expected inputs and outputs without reading the implementation. For more on type hints, see the article on advanced Python type hints with generics.
Module-level docstrings
Every Python file that is meant to be imported should start with a module docstring. It describes what the module provides and how to use it. The docstring goes before any imports:
"""Utility functions for validating and normalizing user input.
This module provides validation functions for common input types like
email addresses, phone numbers, and URLs. Each function returns a
boolean and does not raise exceptions for invalid input.
Example:
from myapp.validation import is_valid_email
if not is_valid_email(user_input):
raise ValueError("Invalid email address")
"""
import re
EMAIL_PATTERN: re.Pattern = re.compile(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
def is_valid_email(address: str) -> bool:
"""Return True if address matches the standard email pattern."""
return bool(EMAIL_PATTERN.match(address))The example section shows a quick usage pattern. Many documentation generators render this as the first thing a user sees when they look up the module.
Documenting classes
A class docstring describes what the class represents and its main public interface. List the most important methods by name and briefly describe their purpose. Do not repeat the method docstrings:
class ShoppingCart:
"""A mutable collection of items with quantity tracking and total calculation.
Public methods:
add_item: Add a product with an optional quantity.
remove_item: Remove a product from the cart.
total: Return the total price of all items.
clear: Remove all items from the cart.
"""
def __init__(self):
self._items: dict[str, int] = {}
def add_item(self, product_id: str, quantity: int = 1) -> None:
"""Add quantity units of product_id to the cart."""
self._items[product_id] = self._items.get(product_id, 0) + quantityDocumentation-driven habits
Write the docstring before you write the function body. This forces you to clarify what the function should do before you get lost in how to do it. If you cannot write a clear one-line summary, the function's purpose is probably unclear and needs more thought.
Update the docstring when you change the function. A docstring that says "returns a list" when the function now returns a generator is actively harmful. Treat the docstring as part of the function's contract, not an afterthought.
Use the help() function to verify your docstrings during development. If help(your_function) does not give you enough information to use the function correctly, the docstring needs more detail.
For more on writing maintainable code, see Python functions explained.
Rune AI
Key Insights
- Use triple-quoted docstrings for modules, classes, and public functions.
- Follow PEP 257: one-line summary, blank line, then detailed description.
- Write comments that explain why, not what the code already says.
- Use type hints to document function signatures and variable types.
- Keep documentation close to the code it describes; outdated docs are worse than no docs.
Frequently Asked Questions
What is the difference between a comment and a docstring?
Should every function have a docstring?
Conclusion
Good documentation is not about writing more words. It is about writing the right words in the right places so the next developer, including your future self, can understand the code without tracing through every line.
More in this topic
How to Test Python Code
Learn what testing means in Python, the core ideas behind automated tests, and the built-in and third-party tools that make testing practical.
Write Your First Python Unit Test
Write and run your first Python unit test step by step, using unittest to check a small function and understand the test output.
The Factory Pattern in Python
Learn how to use the factory pattern in Python to encapsulate object creation, making your code more flexible and easier to extend.