Docstrings, type hints, and basic doc generation
Writing clear documentation and specifying types in your code helps others (and your future self) understand how to use your functions and modules. Python provides tools for this without requiring separate documentation files: docstrings embedded in code and type hints to indicate the expected types of parameters and returns. These can even be used to auto-generate documentation.
Docstrings: A docstring is a string literal that occurs as the first statement in a module, function, class, or method definition. This string is stored by Python as the __doc__ attribute of that object. The primary purpose of a docstring is to document what the function (or module, class, etc.) does – its behavior, arguments, return value, exceptions, and any other relevant details.
How to write a docstring? Simply include a string (often triple-quoted for multi-line) right after the function signature. For example:
def add(a, b):
"""Return the sum of two arguments a and b."""
return a + b
This one-line docstring describes the function’s purpose. We could expand it to multiple lines if necessary:
def add(a, b):
"""
Add two numbers.
Args:
a (int or float): The first number.
b (int or float): The second number.
Returns:
int or float: The sum of a and b.
"""
return a + b
This follows a common format (Google style in this case) where we list the arguments and returns with their types and meanings. Docstrings can use various conventions (Google style, NumPy style, reStructuredText, etc.), but the key is to clearly explain the function’s interface and behavior. The first line is usually a short summary (one line) followed by a blank line, then more details if needed.
Why write docstrings? They serve as inline documentation. Tools like Python’s built-in help() function will display the docstring to the user. For example, after defining the add function above, if you call help(add), you would see something like:
Help on function add in module __main__:
add(a, b)
Add two numbers.
Args:
a (int or float): The first number.
b (int or float): The second number.
Returns:
int or float: The sum of a and b.
This output is generated from the docstring. Similarly, IDEs and text editors can show tooltips or documentation pop-ups based on docstrings.
At the module level, you can have a module docstring at the top of your .py file (before any code) that describes the module’s purpose and contents. Class and method docstrings describe what the class or method does.
Accessing Docstrings: As mentioned, help() is a convenient way to view them. You can also access the __doc__ attribute of a function (e.g., print(add.__doc__)) to get the raw docstring text.
Good Practices for Docstrings:
-
Be concise but clear in the first line (it should be a standalone summary).
-
In a multi-line docstring, the second line should be blank, starting the detailed description on the third line (this is a convention recommended by PEP 257).
-
Document parameters (
Args:orParameters:) and return value (Returns:), including types if not obvious. -
Mention any exceptions the function raises, or any side effects, if relevant.
-
Keep line width reasonable for readability.
Writing docstrings might feel like extra work, but it greatly helps anyone using or maintaining the code. Consistently documented codebases are much easier to navigate.
Type Hints (Type Annotations):
Python is dynamically typed, meaning it doesn’t require type declarations. However, since Python 3.5, the language supports optional type hints (PEP 484) – you can annotate your function parameters and return value with types. These don’t change how the code runs (no runtime type checking by default), but they serve as documentation and enable static analysis tools (like mypy, or IDE features) to catch type inconsistencies.
Syntax: You add a colon and type after parameter names, and -> type after the closing parenthesis to indicate the return type. For example:
def greet(name: str) -> str:
return f"Hello, {name}"
This function is annotated to take a str and return a str. We can also annotate variables (Python 3.6+):
age: int = 42
This tells readers (and type-checkers) that age is intended to be an integer.
Usage in functions:
def add(a: int, b: int) -> int:
return a + b
Now the intention is clear that a and b should be integers and the result is an integer. If someone mistakenly calls add("5", "6"), the code will still run (leading to string concatenation "56" in this case, since Python doesn’t enforce types at runtime), but a static type checker would flag this as a type error. Type hints make your code easier to understand and debug by indicating what types are expected. They also help with editor auto-completion and documentation, and can catch mistakes like passing the wrong type of argument.
You can specify types for built-in types (int, float, str, bool), custom classes, or more complex types like lists or dictionaries (using the typing module, e.g., List[int] for a list of integers, Dict[str, int] for a dict mapping strings to ints, etc.).
Example with type hints and docstring:
def divide(x: float, y: float) -> float:
"""
Divide x by y.
Args:
x (float): The numerator.
y (float): The denominator.
Returns:
float: The result of x / y.
Raises:
ValueError: If y is zero.
"""
if y == 0:
raise ValueError("Denominator cannot be zero.")
return x / y
Here we used both type hints and a detailed docstring. The type hints immediately tell us this function expects two floats and returns a float. The docstring provides additional information, including the fact it raises an exception for invalid input.
Benefits of Type Hints:
-
They provide a form of documentation right in the function signature.
-
Tools can use them to check the consistency of your code.
-
They enhance IDEs: many modern IDEs will show function signatures with types and use that for auto-completion or linting.
Keep in mind: Type hints are optional. They do not cause Python to enforce types at runtime (Python will not throw a type error if you pass the wrong type; it’s up to the developer or external tools to check). If you want runtime enforcement, you would need to add manual checks or use libraries/decorators that enforce types.
Basic Documentation Generation:
Once you have docstrings (and possibly type hints) in your code, you can generate external documentation from them. There are tools like Sphinx that can auto-generate HTML or PDF documentation by parsing your code’s docstrings. For example, Sphinx can use your module and function docstrings to create a nicely formatted reference guide. In your docstrings, you might use reStructuredText or Markdown, which Sphinx can translate into rich text in documentation pages.
Additionally, Python’s built-in pydoc module can generate documentation. Running:
pydoc <module_name>
In the terminal, or python -m pydoc <module_name>) will show the documentation for that module, including all functions and their docstrings. pydoc -w <module_name> can write HTML documentation for a module.
There’s also doctest (a module that allows you to write interactive examples in docstrings and have them automatically tested), which turns documentation into tests.
Docstring Tools:
-
Sphinx: for generating documentation websites from code.
-
doctest: to run examples in docstrings and verify they produce the expected output.
-
introspection: the
help()function and IDE help systems rely on docstrings.
Using these, you can maintain documentation as part of your code. This ensures it’s less likely to become outdated, since when you update the code, you see the documentation right there to update as well.
Example of Combining It All:
def factorial(n: int) -> int:
"""
Compute the factorial of a non-negative integer.
Args:
n (int): A non-negative integer.
Returns:
int: The factorial of n.
Raises:
ValueError: If n is negative.
Examples:
>>> factorial(0)
1
>>> factorial(5)
120
"""
if n < 0:
raise ValueError("Input must be non-negative")
result = 1
for i in range(2, n+1):
result *= i
return result
This code uses a docstring to describe the function, including an Examples section that doctest can use to verify correctness. The type hint n: int -> int communicates expected input and output types. If we run help(factorial), we’d see the documentation, and if we run doctests (python -m doctest mymodule.py), it will execute the examples in the docstring to check correctness.
Summary: Docstrings and type hints significantly contribute to code quality:
-
Docstrings make your code self-documenting.
-
Type hints make code intentions clear and enable static checking.
-
Tools like
doctestand Sphinx leverage these to validate and produce documentation automatically.
Together, they help in building robust, maintainable, and well-documented Python codebases.
