How to document code in Python
Learn how to document Python code effectively. Discover tips, real-world examples, and how to debug common documentation errors.

Effective code documentation in Python is a fundamental practice for developers. It ensures your code is understandable, maintainable, and easy for others to collaborate on, saving time and effort.
In this article, you'll explore essential techniques and practical tips for documentation. You'll find real world applications and specific advice that helps with debugging, which makes your code more professional and easier to manage.
Using # for basic comments
# This is a simple function that adds two numbers
def add_numbers(a, b):
return a + b # Return the sum
result = add_numbers(5, 3)
print(result)--OUTPUT--8
The hash symbol (#) is your go-to for single-line comments in Python, which the interpreter ignores. The example demonstrates two primary uses for adding context to your code:
- A full-line comment, like
# This is a simple function that adds two numbers, explains the purpose of the code block that follows. It gives developers a quick summary before they dive into the implementation details. - An inline comment, such as
# Return the sum, clarifies the action of a specific line. This is especially useful for explaining complex calculations or non-obvious logic right where it occurs.
Basic documentation techniques
While single-line comments are great for quick notes, you'll need docstrings, which use triple quotes like """this""", for more formal and structured documentation.
Using triple-quoted """ docstrings
"""This module demonstrates basic Python documentation.
It contains examples of proper Python documentation practices.
"""
print("Module with documentation imported successfully")--OUTPUT--Module with documentation imported successfully
This example shows a module-level docstring using triple quotes ("""). When you place it at the very top of a Python file, it becomes the official documentation for the entire module. It’s the first thing another developer sees, giving them a high-level summary of the module's purpose.
- Unlike regular comments, docstrings are accessible at runtime. You can view them using Python's built-in
help()function or by accessing the module's__doc__attribute.
Documenting functions with docstrings
def calculate_area(radius):
"""Calculate the area of a circle.
Args:
radius (float): The radius of the circle
Returns:
float: The area of the circle
"""
return 3.14 * (radius ** 2)
print(calculate_area(2))
print(calculate_area.__doc__)--OUTPUT--12.56
Calculate the area of a circle.
Args:
radius (float): The radius of the circle
Returns:
float: The area of the circle
For functions like calculate_area, the docstring explains exactly how to use it. It starts with a quick summary, then uses structured sections like Args: and Returns: to detail its inputs and outputs.
Args:describes each parameter the function expects, including its name and type, such asradius (float).Returns:explains what the function gives back and its type.
This standardized format is great because development tools can read it to offer better help. As the code demonstrates, you can also access this information directly using the __doc__ attribute.
Documenting classes with docstrings
class Rectangle:
"""A class to represent a rectangle.
Attributes:
width (float): The width of the rectangle
height (float): The height of the rectangle
"""
def __init__(self, width, height):
"""Initialize the rectangle with width and height."""
self.width = width
self.height = height
print(Rectangle.__doc__)--OUTPUT--A class to represent a rectangle.
Attributes:
width (float): The width of the rectangle
height (float): The height of the rectangle
When documenting a class like Rectangle, the docstring gives a high-level summary of its purpose. It also introduces a new section for class-specific information.
- The
Attributes:section lists the properties an object will have, such aswidthandheight. This clarifies what data the class is designed to hold.
You'll also notice that methods within the class, like the __init__ constructor, get their own docstrings to explain their specific roles.
Advanced documentation approaches
Beyond the basics, you can make your documentation more powerful and machine-readable by incorporating type hints and structured formats for automated tools.
Using type hints for better documentation
from typing import List, Dict, Tuple
def process_data(items: List[int], config: Dict[str, str]) -> Tuple[int, str]:
"""Process data with type hints for better documentation."""
total = sum(items)
message = config.get('message', 'Completed')
return total, message
print(process_data([1, 2, 3], {'message': 'Success'}))--OUTPUT--(6, 'Success')
Type hints are a powerful way to document your code directly in the function signature. In the process_data function, they tell you exactly what data to use without needing to read the function's logic.
items: List[int]specifies thatitemsmust be a list of integers.config: Dict[str, str]indicates a dictionary with string keys and values.-> Tuple[int, str]shows the function returns a tuple containing an integer and a string.
This level of clarity helps prevent bugs and makes your code easier for development tools to analyze.
Creating Sphinx-compatible docstrings
def divide(a, b):
"""Divide two numbers.
:param a: The dividend
:type a: float
:param b: The divisor
:type b: float
:raises ZeroDivisionError: If b is zero
:return: The quotient
:rtype: float
"""
return a / b
print(divide(10, 2))--OUTPUT--5
The divide function's docstring is formatted for Sphinx, a tool that automatically generates professional documentation from your code. This style, known as reStructuredText, uses specific keywords called directives to structure the information so machines can read it.
- Keywords like
:paramand:typedefine each input, while:returnand:rtypedescribe the output. - The
:raisesdirective is especially helpful because it explicitly warns other developers about potential errors, such as aZeroDivisionError.
Using the __annotations__ attribute
def analyze_text(text: str, max_length: int = 100) -> dict:
"""Analyze text and return statistics."""
stats = {'length': len(text), 'words': len(text.split())}
return stats
print(analyze_text.__annotations__)
print(analyze_text("Hello Python documentation!"))--OUTPUT--{'text': <class 'str'>, 'max_length': <class 'int'>, 'return': <class 'dict'>}
{'length': 26, 'words': 3}
Python automatically stores a function's type hints in a special dictionary called __annotations__. When you define a function like analyze_text, this attribute collects all the type information you've provided for its parameters and return value.
- The output shows this dictionary, where keys are the parameter names—plus a special
'return'key—and the values are their corresponding types. - This allows your code and other development tools to inspect a function’s expected data types at runtime, which is great for validation or creating dynamic documentation.
Move faster with Replit
Replit is an AI-powered development platform that comes with all Python dependencies pre-installed, so you can skip setup and start coding instantly. Instead of piecing together individual techniques, you can use Agent 4 to build a complete application directly from a description. It handles writing the code, connecting databases, and even deployment.
You can take the concepts from this article and build a real product. For example, you could ask the Agent to create:
- A text analysis tool that provides word and character counts, based on the logic in
analyze_text. - A geometry calculator that computes the area of a circle, extending the
calculate_areafunction. - A data dashboard widget that sums numerical inputs and displays a summary, similar to the
process_datafunction.
Simply describe your app, and Replit will write the code, test it, and fix issues automatically, all within your browser.
Common errors and challenges
Even with the best intentions, a few common pitfalls can trip you up when documenting your Python code.
Debugging inconsistent docstring indentation
Python's strict indentation rules extend to docstrings, especially when documentation generators are involved. If one line of a multi-line docstring is indented differently from the others, it can break the layout in the final documentation, making it unreadable.
- To fix this, ensure that all lines within the docstring align consistently. The indentation of the first line after the opening
"""sets the standard for the entire block.
Fixing missing parameters in docstrings with optional arguments
It’s easy to overlook parameters with default values, but leaving them out of your docstring makes your function's behavior unclear. A developer reading your documentation might not know that an optional argument exists, limiting how they use your code.
- Always document every parameter, including optional ones. Clearly state that the parameter is optional and mention its default value, so others can understand the function's full capabilities.
Correcting type hints in function signatures
Type hints are only helpful when they're accurate. A common mistake is mismatching the hint with the actual data type a function uses or returns. For example, hinting that a function returns an int when it actually returns a float can mislead both developers and automated tools.
- You should also remember to import complex types like
List,Dict, orTuplefrom thetypingmodule. Forgetting the import statement will cause aNameErrorat runtime.
Debugging inconsistent docstring indentation
Inconsistent indentation in a docstring can make your documentation look messy and hard to read. Even a single misplaced line can throw off the entire format. Notice how the width parameter is misaligned in the following calculate_area function's docstring.
def calculate_area(length, width):
"""Calculate the area of a rectangle.
Args:
length (float): The length of the rectangle
width (float): The width of the rectangle
Returns:
float: The area of the rectangle
"""
return length * width
The misaligned width parameter causes documentation generators to misinterpret the structure. They'll render the arguments as a single, jumbled line, making the output confusing. The corrected version below shows how to fix this formatting issue.
def calculate_area(length, width):
"""Calculate the area of a rectangle.
Args:
length (float): The length of the rectangle
width (float): The width of the rectangle
Returns:
float: The area of the rectangle
"""
return length * width
The fix is simple: ensure every line in the docstring has the same level of indentation. In the corrected calculate_area function, the width parameter is now properly aligned under length.
- This consistent spacing is crucial for automated documentation tools, which rely on it to correctly parse and render the information. Pay close attention to this when working with any multi-line docstring, as even one misplaced space can break the final output.
Fixing missing parameters in docstrings with optional arguments
Optional parameters are easy to miss when writing documentation, which can hide a function's full potential. A developer might not realize an argument is available if it's missing from the docstring. The format_name function below demonstrates this exact problem.
def format_name(first, last, middle=None):
"""Format a person's name.
Args:
first (str): First name
last (str): Last name
Returns:
str: Formatted full name
"""
if middle:
return f"{first} {middle} {last}"
return f"{first} {last}"
The docstring for format_name fails to mention the optional middle parameter. This makes the function's documentation incomplete, as it hides the ability to include a middle name. See how the corrected version below addresses this.
def format_name(first, last, middle=None):
"""Format a person's name.
Args:
first (str): First name
last (str): Last name
middle (str, optional): Middle name. Defaults to None.
Returns:
str: Formatted full name
"""
if middle:
return f"{first} {middle} {last}"
return f"{first} {last}"
The corrected docstring for format_name now properly includes the optional middle parameter. This simple addition makes the function's full capabilities clear, ensuring other developers know they can provide a middle name.
- By describing it as
(str, optional)and noting that itDefaults to None, you leave no room for guesswork. Always make sure your docstrings account for every parameter, especially those with default values, to prevent confusion.
Correcting type hints in function signatures
Incorrect type hints can create silent bugs that are hard to track down. When a function's signature promises one data type but actually uses another, it misleads both developers and automated tools. The count_words function below shows this exact problem.
def count_words(text: str, ignore_case: str = True) -> int:
"""Count the number of words in a text.
Args:
text (str): The text to analyze
ignore_case (bool): Whether to ignore case
Returns:
int: The number of words
"""
if ignore_case:
text = text.lower()
return len(text.split())
The count_words function’s signature incorrectly hints that ignore_case is a string, but its default value is True, a boolean. This contradiction misleads developers and can cause runtime errors. The corrected version below resolves this inconsistency.
def count_words(text: str, ignore_case: bool = True) -> int:
"""Count the number of words in a text.
Args:
text (str): The text to analyze
ignore_case (bool): Whether to ignore case
Returns:
int: The number of words
"""
if ignore_case:
text = text.lower()
return len(text.split())
In the corrected count_words function, the type hint for the ignore_case parameter is changed from str to bool. This simple fix aligns the hint with its default value of True and its actual use in the code. Mismatched type hints can mislead static analysis tools and other developers, creating confusion and potential bugs.
- It’s crucial to ensure your type hints accurately reflect the data your function expects, especially for optional parameters.
Real-world applications
With your documentation skills sharpened, you can now build powerful, real-world tools that practically document themselves.
Building self-documenting CLI tools with argparse
Python's argparse module lets you create command-line tools that automatically generate help messages, making them easy for anyone to use.
import argparse
def create_calculator_cli():
"""Create a command-line calculator with self-documenting help."""
parser = argparse.ArgumentParser(description="A simple calculator utility.")
parser.add_argument('--add', nargs=2, type=float, help="Add two numbers")
parser.add_argument('--multiply', nargs=2, type=float, help="Multiply two numbers")
return parser
cli = create_calculator_cli()
print(cli.format_help())
The create_calculator_cli function uses the argparse module to define the structure of a command-line tool. By calling parser.add_argument(), you're not just creating functionality—you're also documenting it on the fly.
- The
helpparameter for--addand--multiplyprovides a description for each command. nargs=2andtype=floatspecify that each command expects two numerical inputs.
When cli.format_help() is called, argparse assembles these details into a clean, readable guide, showing users exactly how to interact with your script from the terminal.
Creating interactive documentation with doctest
Python's doctest module allows you to embed runnable examples directly into your docstrings, turning your documentation into a set of verifiable tests.
def calculate_discount(price, percentage):
"""Calculate the final price after applying a discount.
Examples:
>>> calculate_discount(100, 20) # 20% off $100
80.0
>>> calculate_discount(50, 10) # 10% off $50
45.0
"""
return price * (1 - percentage / 100)
if __name__ == "__main__":
import doctest
doctest.testmod()
print("All doctests passed successfully")
The calculate_discount function's docstring pulls double duty as both documentation and a test suite. The lines starting with >>> are interactive examples that show how the function works and what it should return.
- When the script is run directly, the
if __name__ == "__main__"block executes. - The
doctest.testmod()function then automatically finds these examples and runs them. - It verifies that the function's actual output matches the expected results in the docstring, ensuring your documentation is always accurate.
Get started with Replit
Now, turn these concepts into a real tool. Tell Replit Agent to "build a CLI calculator for geometry formulas" or "create a web app that provides text analysis statistics."
The Agent will write the code, test for errors, and deploy your application for you. Start building with Replit.
Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.
Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.

.png)

.png)