Skip to main content Brad's PyNotes

PEP 257: Docstring Conventions for Self-Documenting Code

TL;DR

PEP 257 defines standardized conventions for Python docstrings, covering how to document modules, functions, classes, and methods using string literals that become the __doc__ attribute. The conventions emphasize triple double quotes, command-style phrasing, and consistent formatting.

Interesting!

PEP 257 itself acknowledges these are conventions, not laws: “If you violate these conventions, the worst you’ll get is some dirty looks”. But as the PEP 20 Zen of Python principle 7 notes - “readability counts”!

What Makes a Good Docstring?

PEP 257 distinguishes between conventions (recommended practices) and requirements. All modules, exported functions, classes, and public methods should include docstrings as their first statement.

python code snippet start

def get_user_by_id(user_id):
    """Return user object for the given ID."""
    return database.users.find_one({"_id": user_id})

# Access the docstring at runtime
print(get_user_by_id.__doc__)
# Output: Return user object for the given ID.

python code snippet end

One-Line Docstrings

For simple, obvious cases, one-line docstrings keep both opening and closing quotes on the same line:

python code snippet start

# Good - prescriptive command ending with period
def clear_cache():
    """Clear all cached data from memory."""
    cache.clear()

# Bad - descriptive instead of prescriptive
def clear_cache():
    """This function clears the cache."""
    cache.clear()

# Bad - restates the obvious signature
def square(x):
    """square(x) -> x squared"""  # Don't repeat signature
    return x * x

# Good - describes what it returns
def square(x):
    """Return the square of x."""
    return x * x

python code snippet end

Key principles for one-liners:

  • Use commands (“Return”, “Calculate”, “Send”) not descriptions
  • End with a period
  • Don’t restate the function signature
  • Focus on the return value when applicable

Multi-Line Docstrings

Multi-line docstrings start with a summary line, followed by a blank line, then detailed information:

python code snippet start

def calculate_compound_interest(principal, rate, years, compound_freq=1):
    """Calculate compound interest with flexible compounding frequency.

    This function computes the final amount after applying compound
    interest over a specified period with customizable compounding frequency.

    Args:
        principal (float): Initial investment amount in dollars
        rate (float): Annual interest rate as decimal (0.05 for 5%)
        years (int): Number of years to compound
        compound_freq (int): Times per year to compound (default: 1)

    Returns:
        float: Final amount after compound interest

    Raises:
        ValueError: If principal or rate is negative
    """
    if principal < 0 or rate < 0:
        raise ValueError("Principal and rate must be non-negative")

    return principal * (1 + rate / compound_freq) ** (years * compound_freq)

python code snippet end

Class and Method Docstrings

Class docstrings summarize behavior and list public methods, while the __init__ constructor gets its own docstring:

python code snippet start

class UserAccountManager:
    """Manage user account operations and authentication.

    This class provides methods for creating, validating, and managing
    user accounts including password verification and session handling.

    Public Methods:
        create_account: Register a new user account
        authenticate: Verify user credentials
        reset_password: Initiate password reset process

    Attributes:
        max_attempts (int): Maximum login attempts before lockout
    """

    def __init__(self, max_attempts=3):
        """Initialize the account manager.

        Args:
            max_attempts (int): Maximum login attempts allowed (default: 3)
        """
        self.max_attempts = max_attempts

    def authenticate(self, username, password):
        """Verify user credentials and create session.

        Args:
            username (str): User's login name
            password (str): User's password (will be hashed)

        Returns:
            Session: Active session object if successful

        Raises:
            AuthenticationError: If credentials are invalid
        """
        # Implementation here
        pass

python code snippet end

Module Docstrings

Module docstrings appear at the top of files and describe the module’s purpose and exported items:

python code snippet start

"""User authentication and authorization utilities.

This module provides functions and classes for managing user authentication,
including password hashing, session management, and permission checking.

Exported Classes:
    UserAccountManager: Main authentication handler
    Session: User session representation

Exported Functions:
    hash_password: Securely hash passwords with salt
    verify_password: Compare password against stored hash

Example:
    >>> from auth import UserAccountManager
    >>> manager = UserAccountManager()
    >>> session = manager.authenticate('alice', 'secret123')
"""

import hashlib
from datetime import datetime

python code snippet end

Override vs Extend

When documenting inherited methods, PEP 257 distinguishes between replacement and augmentation:

python code snippet start

class BaseHandler:
    def process(self, data):
        """Process incoming data and return result."""
        return self.transform(data)

class CustomHandler(BaseHandler):
    def process(self, data):
        """Override process to add validation step."""
        if not self.validate(data):
            raise ValueError("Invalid data")
        return super().process(data)

class LoggingHandler(BaseHandler):
    def process(self, data):
        """Extend process to log all operations."""
        result = super().process(data)
        self.log_operation(data, result)
        return result

python code snippet end

Formatting Best Practices

PEP 257 emphasizes consistency over dogma:

python code snippet start

# Good - closing quotes on separate line for multi-line
def complex_operation():
    """Perform complex multi-step operation.

    This function coordinates several subsystems to achieve
    the desired outcome with proper error handling.
    """
    pass

# Good - stay on one line if it fits
def simple_operation():
    """Return True if operation succeeds."""
    return True

# Bad - don't use uppercase for parameter names in prose
def bad_docs(user_id):
    """Find user with USER_ID."""  # Don't do this
    pass

# Good - use lowercase in prose
def good_docs(user_id):
    """Find user with the given user_id."""
    pass

python code snippet end

PEP 257 provides the foundation for self-documenting Python code where documentation lives alongside implementation. Following these conventions ensures your docstrings work seamlessly with help systems, IDE completion, and documentation generators.

These documentation standards perfectly complement PEP 8's code formatting rules to create consistently styled, well-documented Python code. Docstrings are particularly important when defining Python classes and reusable functions .

Reference: PEP 257 - Docstring Conventions