Python Basics — Docstrings & Annotations

3 minute read

Docstrings & Annotations

Well-documented functions are easier to use, maintain, and debug. Python has built-in support for documentation strings and type annotations.

Writing Docstrings

A docstring is a string literal that appears as the first statement in a function:

def add(a, b):
    """Return the sum of two numbers."""
    return a + b

Multi-Line Docstrings

def divide(a, b):
    """
    Divide a by b and return the result.

    Returns None if b is zero.
    """
    if b == 0:
        return None
    return a / b

Docstring Styles

def calculate_bmi(weight, height):
    """Calculate Body Mass Index.

    Args:
        weight: Weight in kilograms.
        height: Height in meters.

    Returns:
        BMI as a float.

    Raises:
        ValueError: If height is zero or negative.
    """
    if height <= 0:
        raise ValueError("Height must be positive")
    return weight / (height ** 2)

NumPy Style

def calculate_bmi(weight, height):
    """
    Calculate Body Mass Index.

    Parameters
    ----------
    weight : float
        Weight in kilograms.
    height : float
        Height in meters.

    Returns
    -------
    float
        BMI value.

    Raises
    ------
    ValueError
        If height is zero or negative.
    """
    if height <= 0:
        raise ValueError("Height must be positive")
    return weight / (height ** 2)

reStructuredText Style

def calculate_bmi(weight, height):
    """Calculate Body Mass Index.

    :param weight: Weight in kilograms.
    :type weight: float
    :param height: Height in meters.
    :type height: float
    :returns: BMI value.
    :rtype: float
    :raises ValueError: If height is zero or negative.
    """
    if height <= 0:
        raise ValueError("Height must be positive")
    return weight / (height ** 2)

Type Hints / Annotations

Python 3.5+ supports type annotations — hints about what types a function expects and returns:

def add(a: int, b: int) -> int:
    return a + b

def greet(name: str, times: int = 1) -> str:
    return (f"Hello, {name}! " * times).strip()

def is_even(n: int) -> bool:
    return n % 2 == 0

Type hints are not enforced at runtime — they’re for documentation and IDE support.

Common Type Annotations

from typing import Optional, Union

def find_user(user_id: int) -> Optional[dict]:
    """Return user dict or None if not found."""
    pass

def process(value: Union[str, int]) -> str:
    """Accept string or int, return string."""
    return str(value)

# Python 3.10+ simplified syntax
def process(value: str | int) -> str:
    return str(value)

Collection Types

def average(numbers: list[float]) -> float:
    return sum(numbers) / len(numbers)

def count_words(text: str) -> dict[str, int]:
    words = text.split()
    return {w: words.count(w) for w in set(words)}

help() and __doc__

def factorial(n):
    """Calculate the factorial of n (n! = n × (n-1) × ... × 1)."""
    if n <= 1:
        return 1
    return n * factorial(n - 1)

# Access docstring
print(factorial.__doc__)
# Calculate the factorial of n (n! = n × (n-1) × ... × 1).

# Interactive help
help(factorial)
# Help on function factorial:
# factorial(n)
#     Calculate the factorial of n (n! = n × (n-1) × ... × 1).

Combining Docstrings and Type Hints

def search(items: list[str], query: str, case_sensitive: bool = False) -> list[str]:
    """Search for items matching a query string.

    Args:
        items: List of strings to search through.
        query: The search term.
        case_sensitive: Whether the search is case-sensitive. Defaults to False.

    Returns:
        List of matching items.
    """
    if not case_sensitive:
        query = query.lower()
        return [item for item in items if query in item.lower()]
    return [item for item in items if query in item]

Why Documentation Matters

# ❌ Undocumented — what does this do?
def proc(d, k, v):
    if k in d:
        d[k].append(v)
    else:
        d[k] = [v]
    return d

# ✅ Documented — clear purpose and usage
def group_by_key(data: dict, key: str, value) -> dict:
    """Add a value to a grouped dictionary.

    If the key exists, appends to the existing list.
    If not, creates a new list with the value.

    Args:
        data: The dictionary to update.
        key: The grouping key.
        value: The value to add.

    Returns:
        The updated dictionary.
    """
    if key in data:
        data[key].append(value)
    else:
        data[key] = [value]
    return data

Summary

  • Docstrings: first line of a function, in triple quotes
  • Google style is most beginner-friendly
  • Type hints: def f(x: int) -> str: — not enforced at runtime
  • Access with func.__doc__ or help(func)
  • Use Optional[X] for values that could be None
  • Good documentation saves hours of debugging

Next up: Functions — Practice Exercises →

Updated: