Write Documentation Using Docstrings in Python: Complete Function, Class, and Module Documentation Guide

Write documentation using docstrings in python

Write documentation using docstrings in python

When I first started writing Python code, I treated comments and documentation as an afterthought. I’d finish a function, feel proud of myself, and move on without ever explaining what it did. Six months later, I’d open that same file and have absolutely no idea what my past self was thinking. That’s the moment I discovered docstrings, and it genuinely changed how I write code.

A docstring, short for “documentation string,” is Python’s built-in way of attaching documentation directly to modules, classes, functions, and methods. Unlike a regular comment, a docstring isn’t just thrown away by the interpreter — it becomes a real, accessible attribute of the object it documents. That’s the part most beginners miss, and it’s exactly what makes docstrings so powerful.

What Exactly Is a Docstring?

A docstring is simply a string literal that appears as the first statement in a module, function, class, or method definition. Python stores this string in a special attribute called __doc__. I like to think of it as a note that Python actually remembers, rather than a comment that just sits there for humans to read once.

def greet(name):
    """Return a friendly greeting for the given name."""
    return f"Hello, {name}!"

print(greet.__doc__)

Output:

Return a friendly greeting for the given name.

Notice I didn’t use print() explicitly to fetch documentation — Python attached it automatically because the string was the very first line inside the function body.

Single-Line vs Multi-Line Docstrings

I usually reach for a single-line docstring when a function is simple enough that one sentence fully explains it.

def square(x):
    """Return the square of x."""
    return x * x

For anything more complex, I switch to a multi-line docstring, which follows a summary line, a blank line, and then a more detailed explanation.

def calculate_bmi(weight_kg, height_m):
    """
    Calculate Body Mass Index (BMI).

    Parameters
    ----------
    weight_kg : float
        Weight of the person in kilograms.
    height_m : float
        Height of the person in meters.

    Returns
    -------
    float
        The calculated BMI value.
    """
    return weight_kg / (height_m ** 2)

This layout isn’t arbitrary. PEP 257, Python’s official docstring convention, recommends this exact structure: a concise summary line, followed by a blank line, followed by further elaboration.

Module-Level Docstrings

Every well-documented Python file I write starts with a module-level docstring at the very top, before any imports.

"""
inventory_manager.py

This module handles inventory tracking for a small retail store,
including stock updates, low-stock alerts, and reporting.
"""

import json

When someone runs help(inventory_manager) or imports the module and checks inventory_manager.__doc__, they immediately get context about what the file is for, without opening a single function.

Class and Method Docstrings

Classes deserve the same treatment. I document the class itself, and then I document each meaningful method separately.

class BankAccount:
    """
    Represents a simple bank account with deposit and withdrawal operations.

    Attributes
    ----------
    owner : str
        Name of the account holder.
    balance : float
        Current account balance.
    """

    def __init__(self, owner, balance=0.0):
        """Initialize the account with an owner and optional starting balance."""
        self.owner = owner
        self.balance = balance

    def deposit(self, amount):
        """
        Add funds to the account.

        Parameters
        ----------
        amount : float
            The amount to deposit. Must be positive.

        Raises
        ------
        ValueError
            If amount is not positive.
        """
        if amount <= 0:
            raise ValueError("Deposit amount must be positive.")
        self.balance += amount

How Python Stores and Retrieves Docstrings Internally

Here’s the part that fascinated me once I dug into the internals. When the Python compiler parses a function, class, or module and finds a string literal as the very first statement in the body, it doesn’t discard it like it does other unused expression statements. Instead, the compiler recognizes this special case and stores the string in the object’s __doc__ attribute at creation time.

This happens at compile time, not runtime, which is why docstrings have essentially zero performance cost when your code executes normally — they’re just sitting there as metadata. You can verify this by inspecting the bytecode:

import dis

def example():
    """This is a docstring."""
    pass

dis.dis(example)

You won’t see the docstring being “printed” or evaluated as an operation — it’s baked directly into the function object’s __doc__ slot during compilation.

The help() Function and Introspection

Docstrings are what power Python’s built-in help() system. This is genuinely one of my favorite productivity tools when working with unfamiliar libraries.

help(calculate_bmi)

Output:

Help on function calculate_bmi in module __main__:

calculate_bmi(weight_kg, height_m)
    Calculate Body Mass Index (BMI).

    Parameters
    ----------
    weight_kg : float
        Weight of the person in kilograms.
    height_m : float
        Height of the person in meters.

    Returns
    -------
    float
        The calculated BMI value.

This same mechanism is how IDEs like VS Code and PyCharm show you tooltips and parameter hints while you type. It’s also how tools like Sphinx generate full HTML documentation websites automatically from your source code, without you writing a separate documentation file by hand.

Popular Docstring Formats

Over the years I’ve used several docstring styles, and each has its own community and tooling:

Here’s the same function documented in Google style for comparison:

def calculate_bmi(weight_kg, height_m):
    """Calculate Body Mass Index (BMI).

    Args:
        weight_kg (float): Weight of the person in kilograms.
        height_m (float): Height of the person in meters.

    Returns:
        float: The calculated BMI value.
    """
    return weight_kg / (height_m ** 2)

I’d recommend picking one style and sticking with it consistently across your whole project rather than mixing them, since tooling like Sphinx’s napoleon extension expects a specific format to parse correctly.

Real-World and Automation Use Cases

Docstrings aren’t just for humans reading source code in an editor. In my own automation scripts, I’ve built small command-line tools where the docstring of the module itself doubles as the --help text, using the argparse module combined with __doc__:

"""backup_tool.py: Automated backup utility for project directories."""

import argparse

def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("source", help="Directory to back up")
    args = parser.parse_args()

if __name__ == "__main__":
    main()

This means I only have to maintain one description in one place, and it shows up both as documentation and as CLI help output.

Docstrings also integrate with automated testing through doctest, which scans docstrings for interactive Python examples and actually executes them as tests:

def add(a, b):
    """
    Add two numbers together.

    >>> add(2, 3)
    5
    >>> add(-1, 1)
    0
    """
    return a + b

if __name__ == "__main__":
    import doctest
    doctest.testmod()

Running this file will silently pass if both examples produce correct output, or print a detailed failure report if the implementation ever drifts from the documented behavior. I’ve caught real bugs this way simply because my docstring examples stopped matching reality.

Best Practices I Follow

Common Mistakes to Avoid

One mistake I made early on was writing docstrings that just repeated the function name in sentence form, like """This function calculates BMI.""" for a function literally named calculate_bmi. That adds no value. Another common mistake is forgetting the docstring must be the first statement — if you put a comment or blank line before it, Python still treats a string literal appearing later as an unused expression, not as __doc__.

def broken_example():
    # This comment breaks nothing structurally,
    # but the string below still works fine as the first statement.
    """This still works as a docstring."""
    pass

Actually, comments before the docstring are fine since they’re stripped before parsing — but any executable statement before the string literal will prevent it from being recognized as a docstring.

def not_a_docstring():
    x = 1
    """This is NOT a docstring, just an unused string expression."""
    return x

print(not_a_docstring.__doc__)  # None

FAQs

Do docstrings affect performance? No. They’re stored as metadata at compile time and have no runtime execution cost beyond the tiny memory footprint of the string itself.

Can I use docstrings on variables? Not in the traditional __doc__ sense — Python doesn’t support docstrings for individual variables. Tools like Sphinx work around this using special comment conventions, but it’s not native language support.

What’s the difference between a docstring and a comment? A comment starting with # is discarded entirely by the parser and never accessible at runtime. A docstring is a string literal that becomes a retrievable object attribute.

Should every function have a docstring? Not necessarily every single one, but every public-facing function, class, and module should. Tiny private helpers with self-explanatory names are often fine without one.

Which docstring style should I choose? Pick based on your tooling. If you’re using Sphinx with the Napoleon extension, Google or NumPy style works well. If you’re writing pure reST for Sphinx directly, use reST field lists.

Summary

Docstrings turned out to be one of the simplest habits that made the biggest difference in my own codebases. They’re not just comments — they’re structured, introspectable, retrievable documentation that powers help(), IDE tooltips, automated documentation generators like Sphinx, and even automated testing through doctest. Once I started treating docstrings as a core part of writing a function rather than an optional extra, my code became something I could actually return to months later and understand immediately.

References

Exit mobile version