Skip to content

Writing Documentation for Your Software

Documentation serves different audiences and purposes. Good projects usually include several types of documentation.

Why Documentation Matters?

Good documentation is an essential part of professional software development. It makes your code understandable, usable, and maintainable — not only for others, but also for future you.

Types of documentation

We will here talk about the most generic and basic types of documentation, those that each project should contain.

However, depending on your project and especially project type, you could add other documentation types. For example:

  • CLI project: Add a man page (either to the user documentation or as a Unix man page)
  • REST API backend project: Add an OpenAPI specification to document the endpoints (handled automatically by frameworks such as fastapi)

README

The README is the front page of your project repository. It should include:

  • A short project description
  • Installation instructions
  • Basic usage example
  • Links to more detailed docs
  • License, citation info, contact (for research code)

Depending on your project Git Forge, its language could vary (though Markdown is the standard and is always accepted), and it could have a flavour (i.e some extensions to the base language to add features to your basic README file). Check the documentation of your Git Forge on the subject.

Note

For example, this page is written in Markdown, but includes extensions like Mermaid to draw diagrams such as the git commit graphs in Git Essentials, an extension to show a clean table of content in the pages, etc.

User & Developer Guides

These are more detailed documents explaining how to use the software in various contexts.

User documentation includes:

  • How-to guides (e.g at least a getting started describing how to install and a basic example)
  • Examples and tutorials (could be more advanced)
  • Common pitfalls and troubleshooting

Developer documentation includes:

  • How to contribute (very important to set the rules for contributors, see Collaborative Git Workflows)
  • Architecture overview
  • Dependency information
  • Build/test instructions

API Documentation

API docs describe functions, classes, modules, and methods: what they do, what inputs they take, and what they return.

This can be:

Docstring Conventions

In Python, docstrings are the main source of in-code documentation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def add(a, b):
    """
    Add two numbers.

    Args:
        a (int): First number.
        b (int): Second number.

    Returns:
        int: The sum of a and b.
    """
    return a + b

Note

If your project uses type hints, you can skip the type definition from the docstring. Indeed, your documentation generator can be configured to parse them too.

There are 3 common conventions for writing docstrings:

Google Style

1
2
3
4
5
6
7
8
9
def greet(name):
    """Greets a person.

    Args:
        name (str): Person's name.

    Returns:
        str: Greeting message.
    """

Type Hints variant:

1
2
3
4
5
6
7
8
9
def greet(name: str) -> str:
    """Greets a person.

    Args:
        name: Person's name.

    Returns:
        Greeting message.
    """

✅ Readable ✅ Supported by many tools

For more detailed documentation: Google Python Style Guide

NumPy Style

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def greet(name):
    """
    Greets a person.

    Parameters
    ----------
    name : str
        Person's name.

    Returns
    -------
    str
        Greeting message.
    """

Type Hints variant:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def greet(name: str) -> str:
    """
    Greets a person.

    Parameters
    ----------
    name :
        Person's name.

    Returns
    -------
        Greeting message.
    """

✅ Common in scientific codebases

For more detailed documentation: NumPy Docstring Standard

reStructuredText (reST)

1
2
3
4
5
6
7
8
9
def greet(name):
    """
    Greets a person.

    :param name: Person's name
    :type name: str
    :return: Greeting message
    :rtype: str
    """

Type Hints variant:

1
2
3
4
5
6
7
def greet(name: str) -> str:
    """
    Greets a person.

    :param name: Person's name
    :return: Greeting message
    """

✅ Native to Sphinx

For more detailed documentation: reST Documentation

Documentation Generators

You can generate full documentation websites from docstrings and Markdown/ReST files using:

Sphinx

Sphinx is the most popular tool for Python API documentation.

  • Uses reStructuredText (or Markdown via extensions)
  • Works well with NumPy or Google-style docstrings via sphinx.ext.napoleon
  • Integrates with Read the Docs for live hosting

Install and Initialize

1
2
pip install sphinx
sphinx-quickstart

Example conf.py additions

1
2
3
4
5
6
# use autodoc and support Google & Numpy style docstrings
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.napoleon']
# options added to automodule directives in the doc (here add doc for all functions/classes of a module)
autodoc_default_options = {'members': True}
# use type hints for the doc
autodoc_typehints = "description"

Note

You can add more extensions depending on the features you need for your documentation. Some are not native to sphinx and must be installed first. You could for instance use the Read the Docs theme (after installing the package):

1
2
3
4
5
import sphinx_rtd_theme  # noqa: F401

...

html_theme = 'sphinx_rtd_theme'
The # noqa: F401 is an instruction for the linters to not apply this rule to the line (here it's an unused import, but still necessary for sphinx to use the theme).

Build the docs

1
2
3
make html
# or
sphinx-build -b html . _build/html

Warning

When using autodoc, the extension simply processes autodoc directives present in the doc files, and parse the source code accordingly. Those directives must be present in the doc files beforehand.

If you want to generate documentation files with fully setup autodoc directives, you can use the following command from your doc/ directory:

1
sphinx-apidoc -o <api output dir> ../src

You can then include the root doc file created by this command inside your doc pages as a link (or part of the table of content). It will generally be called <api output dir>/<package name>.rst.

Tip

To always include the uptodate version of your API to the documentation, you can modify the doc/Makefile so it generates the API before each doc build. Add this on a new line:

1
2
3
html:
    sphinx-apidoc -o <api output dir> ../src
    @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

MkDocs

MkDocs is a simpler, Markdown-based tool focused on static site documentation.

  • Easy to use
  • Pure Markdown
  • Beautiful themes like material

Install and create docs

1
2
3
4
pip install mkdocs
mkdocs new my-docs
cd my-docs
mkdocs serve  # Live preview

Add API Docs (Optional)

Use mkdocstrings to generate API reference:

Add this to the mkdocs.yml configuration file:

1
2
3
plugins:
  - search
  - mkdocstrings

Add to a .md file:

1
::: my_package.my_module

Build static site

1
mkdocs build

Comments in code

Comments are an often overlooked and essential part of writing clean, understandable, and maintainable code. They help readers (including your future self) understand why certain decisions were made, what complex logic is doing, or how a particular block fits into the bigger picture.

Types of Comments

Python supports two main types of comments:

Single-line Comments

Use the hash symbol # to write a comment on a single line or after a line of code:

1
2
3
4
5
# This function calculates factorial using recursion
def factorial(n):
    if n == 0:
        return 1  # base case
    return n * factorial(n - 1)  # recursive step

✅ Tip: Avoid stating the obvious. Focus on the "why", not the "what".

Block (Multi-line) Comments

Although Python doesn't have a special syntax for block comments, you can stack single-line comments:

1
2
3
4
# This section handles edge cases
# for missing or incomplete data
# before running the main processing pipeline.
handle_missing_data(data)

Not Docstrings

Comments are different from docstrings, which are string literals used for documentation inside functions, classes, and modules:

1
2
3
def foo():
    """This is a docstring, not a comment."""
    pass

Best Practices

✅ Do ❌ Avoid
Explain why something is done, not what is obvious Commenting every single line
Keep comments short and to the point Writing outdated comments
Update or remove comments when code changes Using comments to disable code permanently
Use comments to explain complex or non-intuitive logic Stating what the code clearly expresses on its own
Write comments in English (or the language of your team/project) Using slang or abbreviations that others might not understand

Tools and Formatting

  • Linters like flake8 and ruff can warn about misplaced or excessive comments.
  • Use a consistent style (e.g. one space after #) and keep lines under 79 characters.
  • Document assumptions, limitations, or workarounds clearly.

Summary

Use comments to communicate intent, not just to describe the code. Good comments can reduce onboarding time, ease debugging, and improve team collaboration — but bad or stale comments can mislead just as easily.

Hosting and Live Documentation

You can host your generated documentation using:

  • GitHub Pages (mkdocs gh-deploy)
  • GitLab Pages
  • Read the Docs (for Sphinx projects)
  • Self-hosted HTML

Read the Docs can be setup to automatically build and publish the doc whenever the online git repository is changed. For the other options, it can also be automatized via CI/CD.

Best Practices

  • Keep README up to date and focused
  • Write docstrings for every public function/class/module
  • Use consistent docstring style
  • Generate and preview your docs as part of CI
  • Document corner cases, expected input types, and errors
  • Include examples and diagrams when possible

Resources