8 Essential Docstrings Python Examples for Cleaner Code

Emmanuel Mumba avatar
8 Essential Docstrings Python Examples for Cleaner Code

If you’ve ever inherited a codebase and felt lost, or struggled to explain your own code’s purpose, you understand why documentation matters. In my experience, great docstrings are more than just comments; they are the contract between your code and its users, including your future self. Mastering different docstring formats is a critical skill for any developer, turning confusing modules into self-explanatory tools.


TL;DR: Key Takeaways

  • Choose a Style and Stick with It: Consistency is more important than the specific style. Google and NumPy styles are popular for their readability and tooling support.
  • Leverage Type Hints: Modern Python (3.5+) allows you to declare types in the function signature. This creates a single source of truth that tools like Sphinx can use to auto-generate parts of your documentation, reducing drift.
  • Write for Humans and Tools: Good docstrings are readable by developers but also structured for tools like Sphinx or MkDocs to generate API documentation automatically.
  • Document More Than Just Functions: Remember to document classes, modules, and especially attributes and properties to provide a complete picture of your API.
  • Automate to Prevent Drift: Manual docstring maintenance is prone to error. Tools can help automate updates, ensuring documentation stays in sync with code changes.

This guide moves beyond theory to provide a comprehensive collection of actionable docstrings python examples. We will explore a variety of conventions, each with its own strengths. You will learn not just what to write, but how and why specific styles are chosen.

Table of Contents

1. Google Style Docstrings

Google Style docstrings are one of the most popular conventions, prized for their exceptional readability and structured format. It strikes a balance between being easy for humans to parse and detailed enough for automated documentation generators like Sphinx (with the napoleon extension).

The structure is defined by clear, simple section headers like Args:, Returns:, and Raises:. This design makes it straightforward to understand a function’s purpose without needing to read the source code.

Strategic Breakdown

Here’s an example of a function documented using the Google Style, one of the clearest docstrings python examples you’ll find.

def calculate_ema(prices: list[float], period: int) -> list[float] | None:
    """Calculates the Exponential Moving Average (EMA) for a list of prices.

    The EMA gives more weight to recent prices, making it more responsive to
    new information. It's a widely used indicator in financial analysis.

    Args:
        prices (list[float]): A list of prices (e.g., daily closing prices).
        period (int): The span or period for the EMA calculation. Must be
            a positive integer.

    Returns:
        list[float] | None: A list containing the EMA values. Returns None
        if the input list is too short for the given period.

    Raises:
        ValueError: If the period is not a positive integer.
    """
    if not isinstance(period, int) or period <= 0:
        raise ValueError("Period must be a positive integer.")
    if len(prices) < period:
        return None
    
    # ... calculation logic ...
    return [101.5, 102.3, 102.8] # Dummy return for example

Analysis and Insights

  • Human-First Readability: The indented Args and Returns sections are easy to scan. The format parameter_name (type): description is intuitive.
  • Structured for Tooling: Tools like Sphinx can parse these sections to auto-generate clean API documentation.
  • Clarity on Preconditions and Postconditions: The Raises section explicitly documents potential errors, which is crucial for building robust applications.

2. NumPy Style Docstrings

The NumPy style docstring is a cornerstone of the scientific Python ecosystem, renowned for its detailed and explicit structure. It is more verbose than Google style but offers unparalleled detail, making it a favorite for libraries like SciPy, Pandas, and Scikit-learn.

This format uses distinctive section headers underlined with hyphens, such as Parameters, Returns, and Examples. It places a strong emphasis on detailing parameter types and providing runnable examples.

Strategic Breakdown

Below is a function documented in the NumPy style. This example illustrates how the format handles detailed parameter descriptions.

import numpy as np

def moving_average(data: np.ndarray, window_size: int) -> np.ndarray:
    """Calculate the moving average of a 1D array.

    Parameters
    ----------
    data : np.ndarray
        The input array of numerical data. Must be one-dimensional.
    window_size : int
        The size of the moving window. Must be a positive integer.

    Returns
    -------
    np.ndarray
        An array containing the moving average values. The length of this
        array will be len(data) - window_size + 1.

    See Also
    --------
    numpy.convolve : A more general function for convolution.

    Examples
    --------
    >>> import numpy as np
    >>> series = np.array([10, 20, 30, 40, 50, 60])
    >>> moving_average(series, 3)
    array([20., 30., 40., 50.])
    """
    if not isinstance(window_size, int) or window_size <= 0:
        raise ValueError("window_size must be a positive integer.")
    return np.convolve(data, np.ones(window_size), 'valid') / window_size

Analysis and Insights

  • Explicit and Unambiguous: The NumPy style is exceptionally clear. Each parameter is listed with its type on a separate line (data : np.ndarray).
  • Rich Metadata Sections: It includes optional sections like See Also for cross-referencing and Examples for demonstrating usage. The Examples section is often designed to be executable using doctest.
  • Scientific Computing Standard: This format is the de facto standard in the PyData stack. Adopting it ensures your documentation feels familiar to the target audience.

3. reStructuredText (RST) Docstrings

reStructuredText (RST) is the official documentation standard for Python itself and serves as the backbone for Sphinx. This style is more expressive than others, using explicit directives like :param: and :returns: to create highly structured documentation.

While it has a slightly steeper learning curve, its power and direct integration with Sphinx make it the gold standard for large-scale projects. You can learn more about reStructuredText (RST) docstrings and its syntax to get a deeper understanding.

Strategic Breakdown

Below is a function documented using the RST style. This is one of the most robust docstrings python examples for projects that require formal, publishable documentation.

from typing import List, Optional

def fetch_user_data(user_id: int, include_profile: bool = False) -> Optional[dict]:
    """Fetches user data from the database.

    This function queries the primary database for a user's core data.
    It can optionally include extended profile information.

    :param user_id: The unique identifier for the user.
    :type user_id: int
    :param include_profile: If True, includes the user's profile data.
    :type include_profile: bool
    :returns: A dictionary containing user data or None if user not found.
    :rtype: dict or None
    :raises ConnectionError: If the database connection fails.
    """
    if not isinstance(user_id, int):
        raise ConnectionError("Failed to connect to the database.")
    
    if user_id == 42:
        user_data = {"id": 42, "username": "arthur_dent"}
        if include_profile:
            user_data["profile"] = {"towel_status": "present"}
        return user_data
    return None

Analysis and Insights

  • Explicit and Unambiguous: RST separates the type from the description using :param:, :type:, :returns:, and :rtype:. This verbosity eliminates ambiguity.
  • Built for Sphinx: This format is designed for Sphinx. Directives can be hyperlinked across a documentation site, allowing users to navigate to definitions.
  • Verbosity as a Feature: While it requires more typing, the explicit nature of RST forces developers to be precise.

4. PEP 257 Style Docstrings

PEP 257 provides the foundational conventions for Python docstrings. Its core philosophy is simplicity, ensuring every Python object can have basic, consistent documentation.

The structure is intentionally minimal. It begins with a concise one-line summary, followed by a more detailed description if necessary.

Strategic Breakdown

Here is a function documented using the classic PEP 257 style. This format is one of the most fundamental docstrings python examples.

def find_first_occurrence(elements: list, target: any) -> int | None:
    """Find the index of the first occurrence of a target element.

    This function iterates through a list of elements and returns the
    zero-based index of the first item that matches the target value.
    The iteration stops as soon as a match is found to ensure optimal
    performance for large lists.

    If the target element is not found within the list, the function
    will return None. This allows for a clear distinction between an
    element found at index 0 and a non-existent element.
    """
    for index, element in enumerate(elements):
        if element == target:
            return index
    return None

Analysis and Insights

  • Imperative and Direct: The summary line uses the imperative mood (“Find” instead of “Finds”). This is a key convention of PEP 257.
  • Simplicity is Key: Unlike other styles, PEP 257 does not prescribe special sections for arguments or return values. This information is woven into the main description.
  • Universal Compatibility: As the official standard, this style is universally understood by Python developers and parsable by nearly every documentation tool.

5. Sphinx Autodoc with Type Hints

Modern Python introduced type hints (PEP 484), which changed how we document code. Combining Sphinx with type hints represents a highly efficient, “Don’t Repeat Yourself” (DRY) approach, where type information is written once in the function signature and automatically pulled into the documentation.

This method eliminates the redundant practice of manually specifying types in the docstring.

Strategic Breakdown

Here’s a practical example showing how type hints are seamlessly integrated into a docstring.

from typing import Union

def format_user_data(user_id: int, details: dict[str, Union[str, int]]) -> str:
    """Formats user data into a standardized string representation.

    This function takes a user's ID and a dictionary of details and
    compiles them into a single, readable string summary. It leverages
    Python's type hints to declare its expected inputs and output.

    Args:
        user_id: The unique integer identifier for the user.
        details: A dictionary containing user attributes like 'name' (str)
                 or 'age' (int).

    Returns:
        A formatted string summarizing the user's information.
    """
    name = details.get("name", "N/A")
    age = details.get("age", 0)
    return f"User ID: {user_id}, Name: {name}, Age: {age}"

Analysis and Insights

  • Single Source of Truth: The types (int, dict, str) are defined in the function signature. Sphinx autodoc reads these, so you only describe what the parameters are in the docstring.
  • Enhanced Tooling and IDE Support: IDEs like VS Code and PyCharm use these type hints to provide superior autocompletion, static analysis, and real-time error checking.
  • Improved Maintainability: When a type changes, you only need to update it in one place: the function signature. For more on this, you can explore guides on how to improve your Python function documentation.

6. Markdown Docstrings

Markdown docstrings leverage simple, plain-text formatting to create rich, readable documentation. This modern approach is popular with tools like FastAPI and MkDocs because it bridges the gap between raw source code and polished, web-based docs.

The idea is to embed standard Markdown syntax, such as headers, lists, and code blocks, inside the triple-quoted string.

Strategic Breakdown

Here’s an example of a function using Markdown in its docstring.

def process_user_data(user_id: int, data: dict, *, strict: bool = False) -> dict:
    """Processes raw user data and prepares it for analysis.

    This function cleans, validates, and transforms user data based on a
    predefined schema.

    ### Parameters:
    - `user_id` (int): The unique identifier for the user.
    - `data` (dict): A dictionary containing raw user information.
    - `strict` (bool): If `True`, the function will raise an error on
      any validation failure. Defaults to `False`.

    ### Returns:
    A dictionary with the processed data, ready for downstream tasks.

    ### Example Usage:
    ```python
    user_data = {"name": "   John Doe  ", "email": "[email protected]"}
    processed = process_user_data(101, user_data)
    print(processed)
    ```
    """
    if strict and not data.get("email"):
        raise ValueError("Email is required in strict mode.")
    
    processed_data = {k: v.strip() if isinstance(v, str) else v for k, v in data.items()}
    processed_data["id"] = user_id
    return processed_data

Analysis and Insights

  • Platform-Native Readability: The use of Markdown headers (###) and bullet points (-) makes the docstring easy to read directly on platforms like GitHub.
  • Simple yet Powerful: Markdown offers sufficient formatting without the verbosity of RST. To visually compose Markdown, consider an Online Markdown WYSIWYG editor.
  • Tooling Ecosystem: Modern generators like MkDocs (with plugins like mkdocstrings) parse Markdown docstrings natively, promoting a “docs-as-code” methodology.

7. Epydoc Style Docstrings

Epydoc Style docstrings represent a format heavily influenced by Javadoc, using @-notation to create structured documentation. While largely superseded by modern styles, you will frequently encounter it in older Python projects.

The core idea is to use field lists where each item is marked by an @ tag, like @param or @type.

Strategic Breakdown

Here is a function documented using the Epydoc style, a classic you might find in legacy code.

def get_user_profile(user_id: int, include_details: bool = True) -> dict | None:
    """Fetches a user profile from the database.

    Retrieves user information based on their unique ID. Can optionally
    exclude sensitive details for a summary view.

    @param user_id: The unique identifier for the user.
    @type user_id: int
    @param include_details: If True, include full profile details.
    @type include_details: bool
    @return: A dictionary containing user profile data, or None if not found.
    @rtype: dict or None
    @raise DatabaseError: If a connection to the database cannot be established.
    """
    if not isinstance(user_id, int) or user_id <= 0:
        return None
    
    if user_id == 42:
        if include_details:
            return {"id": 42, "name": "John Doe", "email": "[email protected]"}
        else:
            return {"id": 42, "name": "John Doe"}
    return None

Analysis and Insights

  • Explicit and Unambiguous: The use of distinct tags like @param and @type leaves no room for ambiguity.
  • Verbosity as a Trade-off: The main drawback is its verbosity. Separating the parameter’s name, type, and description can feel redundant.
  • Legacy System Compatibility: Its primary value today is in maintaining consistency within existing codebases that already use it.

8. Attribute and Property Docstrings

While function docstrings get most attention, documenting class attributes and properties is equally critical for a clear API. This pattern focuses on explaining the data a class holds, not just the actions it can perform.

Properly documenting attributes is essential for frameworks like Django or Pydantic where classes act as data structures.

Strategic Breakdown

Here is a class using the reStructuredText format to document its attributes and properties.

class StockPortfolio:
    """Manages a collection of stock holdings.

    This class tracks stock tickers, the number of shares for each, and
    calculates the total current value of the portfolio.

    Attributes:
        _api_key (str): The private API key for a financial data service.
        holdings (dict[str, int]): A dictionary mapping stock tickers (e.g.,
            "AAPL") to the number of shares owned.
    """
    
    _CURRENT_PRICES = {"AAPL": 150.75, "GOOG": 2750.25}

    def __init__(self, api_key: str):
        """Initializes the portfolio with an API key.
        
        Args:
            api_key (str): The API key for fetching real-time data.
        """
        self._api_key = api_key
        self.holdings = {}

    @property
    def total_value(self) -> float:
        """float: The total current market value of all holdings.

        This property calculates the value on-the-fly by multiplying the
        number of shares of each stock by its current price. It is a
        read-only property.
        """
        value = 0.0
        for ticker, shares in self.holdings.items():
            value += shares * self._CURRENT_PRICES.get(ticker, 0)
        return value

Analysis and Insights

  • Centralized Attribute Documentation: The Attributes section provides a single source for understanding the instance’s state.
  • Property-Specific Docstrings: The @property decorator allows a method to be accessed like an attribute. Its docstring is crucial for explaining what it represents and its behavior.
  • Clarifying Public vs. Private: The leading underscore in _api_key signals it as an internal implementation detail, providing context for maintainers.

Comparison of 8 Python Docstring Styles

Style🔄 Complexity⚡ Resource / Tooling📊 Expected Outcomes💡 Ideal Use Cases⭐ Key Advantages
Google Style DocstringsModerate — structured sections, requires manual consistencyMedium — Sphinx support, formatters, IDE hintsClear, human-readable docs; good tooling integration (⭐⭐⭐)General libraries, APIs, industry projectsReadable, widely adopted, balanced detail
NumPy Style DocstringsHigh — strict underlined headers and indentationMedium–High — Sphinx-friendly, needs careful formattingVery detailed param/array docs; excellent for numeric results (⭐⭐⭐⭐)Scientific computing, data analysis, math libsPrecise parameter typing and examples for complex APIs
reStructuredText (RST) DocstringsHigh — RST syntax learning curve, verbose raw markupHigh — Best with Sphinx for full featuresProfessional, cross-referenced docs and rich formatting (⭐⭐⭐⭐)Public libraries, enterprise docs, official docsPowerful cross-references and Sphinx integration
PEP 257 Style DocstringsLow — minimal conventions, simple rulesLow — no special tooling requiredConsistent baseline docs; concise but limited detail (⭐⭐)Simple functions, scripts, foundation for other stylesLightweight, universally applicable baseline
Sphinx Autodoc with Type HintsMedium–High — needs type-hint proficiency and extensionsHigh — Python 3.5+, sphinx-autodoc-typehints, mypy integrationAccurate, single-source type documentation; reduces duplication (⭐⭐⭐⭐)Modern APIs, FastAPI, typed librariesAuto-generated, improves IDE support and type correctness
Markdown DocstringsLow–Medium — familiar syntax, less formal rulesLow — excellent GitHub/MkDocs support; Sphinx needs extra configReadable raw docs and good web rendering (⭐⭐⭐)Modern GitHub projects, MkDocs-based sitesFamiliar, clean, good platform rendering
Epydoc Style DocstringsMedium — @-tag field lists, more verboseLow — limited modern tool support, legacy toolsStructured but dated output; clear tags (⭐⭐)Legacy codebases, older Java-influenced projectsExplicit tags for metadata and unambiguous sections
Attribute & Property DocstringsMedium — must distinguish attribute types and placementVariable — IDEs/tools vary in supportImproved IDE hints and clearer attribute APIs (⭐⭐⭐)Dataclasses, Pydantic models, complex class APIsClarifies attribute behavior, supports autocompletion

Bringing It All Together: Automation and Continuous Documentation

We’ve journeyed through a comprehensive collection of Python docstring examples. The core principle is clear: consistency is king. A project with consistently applied NumPy-style docstrings is far more maintainable than one with a haphazard mix of styles.

But writing the initial docstring is just the first step. The real challenge is keeping that documentation from becoming outdated. Code rarely stays static, and every new feature or refactor introduces the risk of “documentation drift.”

The Problem of Documentation Drift

In a collaborative setting, manual documentation maintenance is fragile. A developer might update a function’s return type but forget to update the docstring. These small omissions accumulate over time, eroding trust in the documentation.

This manual upkeep is not just tedious; it’s a tax on developer productivity. This is where the concept of continuous documentation comes into play, shifting from a manual chore to an automated, reliable process.

Shifting from Manual Upkeep to Continuous Documentation

The goal of continuous documentation is to make accuracy the default state. By integrating documentation updates directly into the development workflow, we can ensure that docstrings and code evolve in perfect sync.

This philosophy is central to what we’re building at DeepDocs. We saw this problem firsthand: teams invest in creating beautiful docstrings, only to see them become outdated. Our solution is a GitHub-native application that acts as a guardian of your documentation.

“The most important thing about documentation is that it is easily available and that it is kept up to date.” – Guido van Rossum, creator of Python

By scanning your repository on every commit, DeepDocs understands the relationships between your code and documentation. When it detects a change like a new parameter it proactively generates an intelligent update for the corresponding docstring, preserving your original formatting and style.

Ultimately, consistent docstring usage contributes significantly to a robust knowledge base. As your project grows, well-maintained docstrings become an invaluable asset, lowering the barrier for new contributors. This is one of the core knowledge management system benefits that directly translates to engineering velocity. By automating the tedious task of synchronization, you empower your team to focus on innovation, confident that their documentation is always accurate.

Tired of your docstrings and READMEs falling out of sync with your code? DeepDocs integrates directly into your GitHub workflow, automatically detecting drift and proposing intelligent updates to keep your documentation accurate. Try DeepDocs for free and make outdated docs a thing of the past.

Leave a Reply

Discover more from DeepDocs

Subscribe now to keep reading and get access to the full archive.

Continue reading