85.1 Docstring Conventions: One-Liners and Multi-Line
Right, let’s talk docstrings. This is where you stop just writing code for the machine and start writing it for the next poor soul who has to read it—which, statistically speaking, is probably you in six months, wondering what past-you was thinking. A docstring is a string literal that lives as the first statement in a module, function, class, or method. It’s not a comment (those are for why, not what). It’s a first-class citizen that the interpreter can see and tools can use to generate beautiful documentation for you. Think of it as the ultimate “explain it like I’m five” note to your future self.
The One-Liner: For the Obvious Stuff
Not every function needs an epic poem. If the function’s name is calculate_total and it takes a price and a quantity, you can probably guess what it does. The one-liner is for these simple, self-explanatory cases. It should be a single, imperative sentence: “Calculate the total price.” Not “Calculates the total price.” or “This function will calculate the total price.” Just state the fact.
def add_tax(amount, tax_rate):
"""Calculate the final amount including tax."""
return amount * (1 + tax_rate)
The key here is brevity. If you find yourself writing a paragraph, you’ve wandered into multi-line territory. The closing triple quotes should be on the same line. This isn’t a style choice; it’s a convention so tools can easily find and extract the summary.
The Multi-Line Epic: When Things Get Real
The moment your function takes more than two parameters, has a non-trivial return type, or might throw something other than a SyntaxError, it’s time for the full docstring treatment. The most common convention, and the one I recommend because every tool under the sun understands it, is Google-style. It’s clean, readable without being rendered, and logically structured.
Here’s the anatomy of a proper multi-line docstring:
def create_user(email, username, password, send_welcome_email=False):
"""Create a new user in the database and optionally send a welcome email.
This function hashes the provided password for security before storing it.
The username is converted to lowercase to ensure consistency.
Args:
email (str): The user's unique email address.
username (str): The user's public display name. Must be unique.
password (str): The user's plaintext password. Will be hashed.
send_welcome_email (bool, optional): If True, triggers an asynchronous
email task. Defaults to False.
Returns:
User: The newly created User model instance.
Raises:
ValueError: If the email or username is already taken.
ConnectionError: If the database is unreachable (e.g., during a network outage).
"""
# ... actual implementation would be here ...
pass
Let’s break down why this works. The first line is the summary, identical to a one-liner. Then, you have a blank line. This is crucial. Tools like Sphinx use that blank line to separate the summary from the rest of the details. After that, you can have a more detailed description if needed. Then come the structured sections:
- Args: Lists each parameter, its expected type (in parentheses), and a description. For optional parameters, state the default value. This is where you prevent 90% of all support questions.
- Returns: Describe the type and the meaning of the return value.
Returns: boolis useless.Returns: True if successful, False otherwiseis marginally better.Returns: The newly created User objectis best. - Raises: This is the most commonly skipped part, and it’s a crime. Documenting the exceptions a function can raise is like telling someone where the landmines are. It’s not optional for robust code.
The Other Convention: Sphinx-style
You might also see Sphinx-style docstrings, which use reStructuredText (reST) markup. They’re powerful but, in my opinion, far less human-readable in their raw form inside the code.
def create_user(email, username, password, send_welcome_email=False):
"""Create a new user in the database and optionally send a welcome email.
:param email: The user's unique email address.
:type email: str
:param username: The user's public display name. Must be unique.
:type username: str
:param password: The user's plaintext password. Will be hashed.
:type password: str
:param send_welcome_email: If True, triggers an asynchronous email task.
:type send_welcome_email: bool
:returns: The newly created User model instance.
:rtype: User
:raises ValueError: If the email or username is already taken.
:raises ConnectionError: If the database is unreachable.
"""
pass
It’s more verbose and the repetition of :param and :type feels clunky. The choice between Google-style and Sphinx-style often comes down to your documentation toolchain, but most modern tools (like MkDocs with the mkdocstrings plugin) handle Google-style perfectly and it’s just easier to write. Pick one and be consistent across your project.
The Biggest Pitfall: Lying
The single worst thing you can do is write a docstring that describes what the function should do, not what it actually does. If you change the implementation from returning a list to returning a generator, you must update the docstring. An out-of-date docstring is actively more harmful than no docstring at all because it breeds misinformation. Treat it with the same respect you treat your code. Because it is code. It’s the code that explains your other code.