PEP 257: Python Docstring Conventions Explained

Good code explains what it does. Great code documents why. PEP 257 is Python's official guide to writing docstrings — the string literals that live inside your functions, classes, and modules and make your code readable to anyone who picks it up, including your future self.

Published in 2001 and co-authored by David Goodger and Guido van Rossum, PEP 257 standardizes how docstrings should look and what they should contain. It does not dictate markup syntax — that is left to tools like Sphinx or NumPy-style formatters — but it does establish a consistent foundation that every Python developer is expected to follow.

Why this PEP had to exist

Before PEP 257, Python documentation was fragmented — even across the standard library itself. Some modules wrote docstrings as prose descriptions. Others mimicked Unix man pages. Some used no consistent structure at all. Tools that tried to generate documentation automatically could not rely on any predictable format. PEP 257 was not a stylistic preference imposed from above: it was the answer to a concrete fragmentation problem that was already slowing Python adoption in larger projects. Every rule in PEP 257 is easier to follow when you understand that it exists to make documentation machine-readable and human-consistent at the same time.

The PEP makes an important point up front: these are conventions, not laws. Violating them will not break your program. But tools that process docstrings — like Docutils, Sphinx, and IDEs — rely on this structure to generate documentation automatically. Consistency also signals professionalism and makes collaboration significantly easier.

Writing to a shared convention, Tim Peters argued on comp.lang.python (June 16, 2001), is what makes maintainability, clarity, and consistency possible — not as separate goals, but as a single outcome. His exact words: "A universal convention supplies all of maintainability, clarity, consistency."

What Is a Docstring?

A docstring is a string literal that appears as the very first statement inside a module, function, class, or method body. Python's interpreter recognizes it and stores it as the object's __doc__ attribute, which means you can access it at runtime.

def greet(name):
    """Return a greeting for the given name."""
    return f"Hello, {name}!"

print(greet.__doc__)
# Output: Return a greeting for the given name.

That direct access to __doc__ is useful for quick checks, but help() is how many readers will encounter your docstrings in practice. It formats the output and adds context from the object's signature:

def fetch_user(user_id, include_inactive=False):
    """Retrieve a user record from the database.

    Returns None if no match exists. Raises ValueError if
    user_id is not a positive integer.
    """
    ...

help(fetch_user)
# Output:
Help on function fetch_user in module __main__:

fetch_user(user_id, include_inactive=False)
    Retrieve a user record from the database.

    Returns None if no match exists. Raises ValueError if
    user_id is not a positive integer.

help() automatically prepends the function signature, which is why PEP 257 warns against writing docstrings that just restate the signature. The reader already has it. The docstring's job is to add what the signature cannot tell them: what None means as a return value, what triggers the exception, what edge cases to expect.

PEP 257 states that all modules, and all public functions and classes exported by a module, should have docstrings. Public methods — including __init__ — should also be documented. A package can be documented inside the docstring of its __init__.py file.

What breaks when you skip it

Omitting a docstring from a public function has real consequences. help(fetch_user) returns only the signature with no explanation. IDE hover tooltips go blank — the developer calling your function sees nothing. Sphinx generates an empty entry in your documentation site. If you publish the module as a package, every user of that API loses the self-service documentation they would otherwise have had. The function still works, but the contract it makes with its callers is invisible.

Note

String literals that appear elsewhere in your code — not as the first statement — are sometimes called "attribute docstrings" or "additional docstrings." They are not assigned to __doc__ and are not accessible at runtime, but certain documentation tools can still extract them. See PEP 258 for details.

PEP 257 requires that docstrings always use triple double quotes: """like this""". If your docstring contains backslashes, use a raw string: r"""raw triple double quotes""". Single quotes are never used for docstrings, even if they would technically work.

Docstrings vs. Comments: What Goes Where

Both docstrings and inline comments explain code, but they serve completely different audiences and purposes. A comment (marked with #) is for anyone reading the source file. It explains why a decision was made, notes a known gotcha, or flags something unusual in the implementation. It disappears entirely at runtime.

A docstring is for anyone using your code — whether they are reading the source or not. It describes what a function does, what it expects, and what it gives back. Because Python stores docstrings in __doc__, they remain accessible at runtime and are picked up by help(), IDEs, and documentation generators. That persistence is what makes the distinction matter:

def calculate_discount(price, rate):
    # rate is a decimal, not a percentage -- e.g. 0.1 for 10%
    # Using multiplication here because subtraction introduced
    # floating point errors in testing (see issue #42).
    """Return the discounted price after applying rate.

    rate must be a decimal between 0 and 1 (e.g., 0.1 for 10%).
    Raises ValueError if rate is outside that range.
    """
    if not 0 <= rate <= 1:
        raise ValueError(f"rate must be between 0 and 1, got {rate}")
    return price * (1 - rate)

The comments explain the implementation — why multiplication was chosen, why a specific format was enforced. The docstring explains the interface — what the caller needs to know to use the function correctly. Someone calling calculate_discount() from another module never sees the comments. They do see the docstring, via help() or their IDE's hover tooltip.

Pro Tip

If you find yourself writing a long inline comment to explain a function's behavior, that content probably belongs in the docstring instead. Comments inside a function body should explain implementation details, not what the function does from the outside.

One-Line Docstrings

A one-line docstring is used when the purpose of a function or method is completely obvious. The entire docstring fits on a single line, and the closing quotes appear on the same line as the opening quotes.

def kos_root():
    """Return the pathname of the KOS root directory."""
    global _kos_root
    if _kos_root: return _kos_root
    ...

A few rules apply specifically to one-liners:

  • Use triple quotes even though the string fits on one line — this makes it easy to expand later.
  • No blank line before or after the docstring.
  • Write the docstring as a command, not a description. Say "Return the pathname", not "Returns the pathname".
  • End the sentence with a period.

One-liner or multi-line? A quick decision guide

The rule sounds simple — use a one-liner when the purpose is "completely obvious" — but "obvious" is subjective. This table makes the call concrete:

Situation Format Reason
Function name and signature already fully communicate what it does one-liner The docstring adds no new information at multi-line length
Function has optional arguments, a non-obvious return value, or can raise exceptions multi-line Callers need behavioral context the signature cannot provide
Function has side effects (writes to disk, mutates a shared object, sends a request) multi-line Side effects are invisible in a signature and must be documented explicitly
You are not sure which to use multi-line A multi-line docstring can always be compressed later; undocumented behavior accumulates quietly
The docstring currently fits on one line but the function is likely to grow multi-line Pre-structuring saves a refactor step; closing quotes on their own line costs nothing
Avoid This

Do not write a docstring that simply repeats the function signature. """function(a, b) -> list""" adds no value. Python's introspection can already tell you the parameters. If the return type is important to document, write it as part of a meaningful description: """Do X and return a list."""

When You Don't Need a Docstring

PEP 257 is clear that not every function requires a docstring. Non-public methods — those whose names start with a leading underscore — are not required to have one. The convention is intentional: internal implementation details that callers are not supposed to use directly don't need the same documentation contract as your public API.

class DataPipeline:
    """Process and transform incoming data records."""

    def run(self, records):
        """Process a list of records and return the results."""
        cleaned = self._clean(records)
        return self._transform(cleaned)

    def _clean(self, records):
        # Strip whitespace and remove empty entries
        return [r.strip() for r in records if r.strip()]

    def _transform(self, records):
        # Apply internal business logic
        return [r.upper() for r in records]

_clean and _transform are implementation details. A comment explaining what they do is enough. The public method run() is the interface callers interact with — that gets a docstring.

A few other cases where a docstring adds more noise than value:

  • Simple property getters where the name is already self-documenting — def name(self): return self._name does not need a docstring explaining that it returns the name.
  • Overridden methods that don't change behavior. If a subclass overrides a method only to call super() with no modifications, the parent's docstring already covers it. Adding an identical docstring to the subclass creates maintenance debt — now there are two copies to keep in sync.
  • Test functions. Test naming conventions (test_returns_none_when_user_not_found) are usually sufficient. Docstrings in test files are rarely surfaced by documentation generators.
Avoid This

Skipping docstrings on non-public methods does not mean skipping all documentation. If an internal method contains complex logic, a brief inline comment explaining the approach is still good practice. The absence of a docstring is not a license to leave the code undocumented — it just means the documentation belongs in a comment rather than a formal docstring contract.

Multi-Line Docstrings

When a function, class, or module needs more explanation than a single sentence can provide, you use a multi-line docstring. The structure is always the same: a summary line, a blank line, then the extended description.

def complex(real=0.0, imag=0.0):
    """Form a complex number.

    Keyword arguments:
    real -- the real part (default 0.0)
    imag -- the imaginary part (default 0.0)
    """
    if imag == 0.0 and real == 0.0:
        return complex_zero
    ...

The summary line serves a specific purpose: automated indexing tools read it to generate quick references. Because of this, it must fit on one line and must be separated from the rest of the docstring by a blank line. The summary can appear on the same line as the opening quotes or on the next line — PEP 257 permits both, though many style guides prefer the same-line form shown above.

Pro Tip

Unless the entire docstring fits on one line, put the closing """ on a line by itself. This is not just stylistic — it makes the docstring compatible with text editor commands like Emacs' fill-paragraph, and it keeps diffs cleaner when you edit the content later.

Docstrings by Context

PEP 257 gives specific guidance for each context where a docstring may appear. What you document — and how much — depends on where you are writing.

Scripts

The docstring for a standalone script should double as its usage message. If someone runs the script with -h or with missing arguments, Python can display the docstring as help text. It should cover the script's purpose, command-line syntax, environment variables, and any files it reads or writes.

Modules

A module docstring should list all the classes, exceptions, functions, and other public objects the module exports, with a brief one-line summary for each. The goal is to give a reader an overview without forcing them to read every function in the file.

Functions and Methods

A function or method docstring should cover its behavior, its arguments (including optional ones and whether keyword arguments are part of the public interface), its return value or values, any side effects, exceptions it may raise, and any restrictions on when it can be called.

def fetch_user(user_id, include_inactive=False):
    """Retrieve a user record from the database.

    Arguments:
    user_id         -- the integer ID of the user to fetch
    include_inactive -- if True, return inactive users as well (default False)

    Returns a User object, or None if no matching record exists.
    Raises ValueError if user_id is not a positive integer.
    """
    ...
Note

PEP 257 explicitly warns against writing argument names in uppercase in the body of a docstring — for example, writing "the USER_ID parameter." Python is case-sensitive, and argument names appear exactly as written in the signature. Always use the correct case.

Classes

A class docstring should describe the class's overall behavior and list its public methods and instance variables. If the class is designed to be subclassed, document the subclassing interface separately. The __init__ method should have its own docstring covering constructor arguments. Individual methods are documented by their own docstrings — not inside the class docstring.

When documenting a subclass, mention the parent class and describe what has changed. PEP 257 reserves specific vocabulary for this: use "override" when a subclass method replaces a parent method entirely, and "extend" when it calls the parent method and adds additional behavior.

class AdminUser(User):
    """A User with elevated privileges for content management.

    Extends User to add role-based access controls and audit logging.
    The save() method is overridden to enforce permission checks before
    writing to the database.

    Public methods:
    can_edit(resource)  -- return True if this user can edit the resource
    audit_log()         -- return the list of recent administrative actions
    """
    ...

PEP 257 also requires inserting a blank line after class docstrings. Class methods are separated from each other by a single blank line, and the docstring needs to be visually offset from the first method — the extra blank line accomplishes this.

Inherited docstrings and subclasses

Python automatically inherits docstrings in subclasses when a method is not overridden at all. If AdminUser inherits a save() method from User and does not override it, AdminUser.save.__doc__ returns the parent's docstring. No work required.

The question arises when you do override a method. There are three cases:

  • Behavior changes completely. Write a new docstring. The parent's no longer applies.
  • Behavior extends the parent. Write a new docstring and use PEP 257's "extend" language to make that explicit: "Extend User.save() to enforce permission checks before writing." Do not rely on the reader finding the parent class to understand what this method does.
  • Behavior is identical but the method must exist in the subclass (for example, to satisfy an interface). A minimal docstring noting that this method follows the parent's contract is enough. Avoid silently leaving __doc__ as None, which happens if you define the method without a docstring at all.
class AdminUser(User):
    def save(self):
        """Override User.save() to enforce permission checks before writing.

        Raises PermissionError if the user lacks write access.
        """
        if not self.has_write_permission():
            raise PermissionError("Write access required.")
        super().save()

Where do constructor arguments go: the class docstring or __init__?

PEP 257 says the __init__ method should have its own docstring covering constructor arguments. In practice, two equally valid conventions are in active use, and the key is to pick one and be consistent:

Convention Where constructor args are documented Common in
Split Class docstring describes the class; __init__ docstring documents the parameters Projects following PEP 257 literally; reST/Sphinx workflows
Consolidated Class docstring documents both the class and its constructor arguments; __init__ has no docstring or a minimal one Google style, NumPy style, many open-source libraries
# Split convention (PEP 257 literal)
class Connection:
    """Manage a single database connection."""

    def __init__(self, host, port=5432, timeout=30):
        """Initialize the connection.

        Arguments:
        host    -- the database hostname
        port    -- the port number (default 5432)
        timeout -- seconds before the connection times out (default 30)
        """
        ...

# Consolidated convention (Google style)
class Connection:
    """Manage a single database connection.

    Args:
        host (str): The database hostname.
        port (int): The port number. Defaults to 5432.
        timeout (int): Seconds before the connection times out. Defaults to 30.
    """

    def __init__(self, host, port=5432, timeout=30):
        ...

The consolidated form is widespread because it puts everything a caller needs in one place — the class docstring is what appears first in help() output and IDE tooltips. Whichever you choose, configure Ruff's convention setting to match so your linter doesn't flag the pattern you have intentionally chosen.

Handling Docstring Indentation

Because docstrings are indented as part of function and class bodies, documentation tools need to know how to strip that indentation before displaying or processing the text. PEP 257 specifies the trimming algorithm precisely.

The rules are:

  • The first line is treated specially: any leading whitespace is stripped entirely.
  • For all remaining lines, the tool finds the minimum indentation across all non-blank lines, then strips exactly that many characters from the left of each line.
  • Blank lines at the beginning and end of the docstring are removed.
  • Relative indentation within the body is preserved.

This means the following two forms are equivalent after trimming:

def foo():
    """A multi-line
    docstring.
    """

def bar():
    """
    A multi-line
    docstring.
    """

PEP 257 includes a reference implementation of the trimming algorithm called trim(). You do not need to call it yourself in everyday coding — documentation tools handle this — but understanding it helps explain why indentation in docstrings behaves the way it does.

Current Tooling (2026)

pydocstyle has been officially deprecated by its maintainers (PyCQA), who now recommend Ruff as the replacement. Ruff provides full parity with pydocstyle's PEP 257 checks, runs significantly faster (written in Rust), and is actively maintained. To enable PEP 257 docstring checks in Ruff, add the following to your pyproject.toml:

[tool.ruff.lint]
select = ["D"]

[tool.ruff.lint.pydocstyle]
convention = "pep257"

Running Ruff as part of your CI pipeline is a low-effort way to keep docstring formatting consistent across a team. If you have an existing codebase that used pydocstyle, Ruff's --fix flag can automatically resolve many common violations.

Beyond PEP 257: Docstring Style Formats

Mental model: two separate layers

PEP 257 and the three style formats below answer different questions and should be thought of as separate layers. PEP 257 is the skeleton — it specifies where docstrings go, what quotes to use, how the summary line is structured, and how indentation is trimmed. The style formats (reST, Google, NumPy) are the flesh — they specify how the extended body marks up parameters, return values, and exceptions. You cannot choose between PEP 257 and Google style; they operate at different levels. Every Google-style or NumPy-style docstring is also a PEP 257-compliant docstring.

PEP 257 defines the structure — where to put the docstring, how to format the summary line, how indentation is stripped. What it deliberately does not define is how to document individual parameters, return values, or raised exceptions. That is left to style conventions that build on the PEP 257 foundation.

Three formats dominate Python projects today. Each is fully PEP 257 compliant at the structural level; they differ only in how they mark up the extended body.

reStructuredText (reST) style

The oldest of the three, reST is the native format for Sphinx, the documentation generator used by CPython itself and thousands of open-source projects. Fields are marked with a colon prefix.

def fetch_user(user_id, include_inactive=False):
    """Retrieve a user record from the database.

    :param user_id: The integer ID of the user to fetch.
    :type user_id: int
    :param include_inactive: If True, return inactive users as well.
    :type include_inactive: bool
    :returns: A User object, or None if no matching record exists.
    :rtype: User or None
    :raises ValueError: If user_id is not a positive integer.
    """
    ...

Google style

Introduced in Google's Python Style Guide, this format uses section headers followed by indented content. It reads more like natural prose than reST and is popular in projects that prioritize readability over strict markup. Sphinx can render it via the napoleon extension.

def fetch_user(user_id, include_inactive=False):
    """Retrieve a user record from the database.

    Args:
        user_id (int): The integer ID of the user to fetch.
        include_inactive (bool): If True, return inactive users
            as well. Defaults to False.

    Returns:
        User or None: A User object, or None if no match exists.

    Raises:
        ValueError: If user_id is not a positive integer.
    """
    ...

NumPy style

Developed for scientific Python libraries like NumPy and SciPy, this format uses underlined section headers that resemble reStructuredText headings. It is common in data science and academic projects and is also rendered by Sphinx via napoleon. It trades compactness for maximum clarity on complex APIs with many parameters.

def fetch_user(user_id, include_inactive=False):
    """Retrieve a user record from the database.

    Parameters
    ----------
    user_id : int
        The integer ID of the user to fetch.
    include_inactive : bool, optional
        If True, return inactive users as well. Default is False.

    Returns
    -------
    User or None
        A User object, or None if no matching record exists.

    Raises
    ------
    ValueError
        If user_id is not a positive integer.
    """
    ...
Which style should you choose?

PEP 257 does not mandate any of these formats. The rule is: pick one and apply it consistently across your entire project. If you use Sphinx, reST is the path of least resistance. If you are writing application code and value plain readability, Google style is widely understood. If you are building a library with complex, heavily parametrized APIs — especially in the data science space — NumPy style gives readers the most precise layout. Ruff can enforce whichever convention you choose via its convention setting.

Docstrings and Type Hints

Since PEP 484 introduced type hints in Python 3.5, there is an ongoing question about how docstrings and type annotations interact. The short answer: they serve different purposes, and using both is the current best practice for public APIs.

Type hints let tools like mypy and editors verify your code statically. Docstrings communicate intent, edge cases, and human-readable context that type annotations cannot express. Consider this example:

def fetch_user(user_id: int, include_inactive: bool = False) -> "User | None":
    """Retrieve a user record from the database.

    Returns None if no matching record exists. Raises ValueError
    if user_id is not a positive integer. Inactive users are
    excluded by default; pass include_inactive=True to include them.
    """
    ...

When type hints are present in the signature, you do not need to repeat the types inside the docstring body — the annotation already carries that information for tools. The docstring's job becomes explaining behavior: what None means in context, what triggers exceptions, what edge cases exist. This keeps docstrings concise without losing the information that matters.

Pro Tip

If you are using Google or NumPy style and your signatures already have type annotations, you can omit the type fields from the docstring parameter sections. Both Sphinx's napoleon extension and Ruff's docstring rules understand this pattern. Writing user_id (int): in your docstring when the signature already says user_id: int adds noise without adding information.

Common Mistakes

Pattern recognition is how fluency is built. These are the errors that appear most consistently in real Python codebases, each shown as a before/after pair so the wrong pattern is easy to spot and the fix is immediately clear.

1. Closing quotes on the wrong line

For multi-line docstrings, the closing """ must appear on a line by itself. Placing it on the last content line is a PEP 257 violation and causes some documentation tools to misparse the docstring boundary.

Why this matters beyond formatting

The isolated closing """ is not cosmetic. Sphinx's parser uses it to determine exactly where the docstring ends before it starts extracting field sections (:param, :returns:, etc.). If the closing quotes share a line with content, Sphinx may silently discard trailing field markup or merge the last sentence into an adjacent section heading. The visual fix — moving three characters to a new line — prevents invisible documentation loss in generated output that you would only notice when your API reference site was already live.

Practically: run ruff check --select D200,D205,D400 . as part of your CI pipeline. These three rules catch the structural violations that most commonly corrupt rendered documentation — closing quote placement is one of them.

WRONG
def send_email(to, subject, body):
    """Send an email message.

    Returns True on success,
    False if delivery failed."""
    ...
CORRECT
def send_email(to, subject, body):
    """Send an email message.

    Returns True on success,
    False if delivery failed.
    """
    ...

2. Writing a description instead of a command

PEP 257 is explicit: use the imperative mood. The docstring should read as an instruction, not a narration. This aligns with how Python's own standard library is written.

The deeper problem with descriptive mood

The imperative mood isn't arbitrary style. It enforces a contract: the docstring makes a promise about what calling the function will do. Third-person descriptive language ("Returns the timestamp") reads like a characterization of the function — what it tends to do — which subtly invites ambiguity about edge cases. Imperative language ("Return the timestamp") reads as a specification that can be held to. This distinction matters when you use docstrings as the basis for test cases, contract-based testing frameworks, or formal API documentation reviewed by non-engineers.

A practical way to catch this across a codebase: ruff check --select D401 . enforces the imperative mood on first-person docstrings. Running it in pre-commit hooks means the habit is enforced at write time, not discovered in code review.

WRONG
def get_timestamp():
    """Returns the current
    UTC timestamp as a string."""
    ...
CORRECT
def get_timestamp():
    """Return the current
    UTC timestamp as a string."""
    ...

3. Restating the signature

A docstring that mirrors what the function signature already says adds zero information. The reader already has the signature. The docstring's job is to explain what the signature cannot: edge cases, what None means, what triggers an exception.

What the docstring should say instead

Signature restatement is common when the author isn't sure what to document. The fix isn't a better paraphrase of the parameters — it's a different category of content entirely. Ask four questions that the signature cannot answer: What does a return value of None (or False or -1) actually mean in this context? Under what conditions does the function raise, and what state is the caller in when that happens? Are there performance characteristics that affect how and when callers should call this? Are there any ordering or precondition constraints — for example, must the caller hold a lock, or must the object be in a particular state? These are the things a signature never tells you, and they are exactly what the docstring exists to communicate.

In legacy codebases where signature restatement is widespread, ruff check --select D417 . flags docstrings that document parameters not present in the signature — a common sign that the docstring was written for an older version of the function and has since drifted.

WRONG
def fetch_user(user_id, flag=False):
    """fetch_user(user_id,
    flag=False) -> User"""
    ...
CORRECT
def fetch_user(user_id, flag=False):
    """Retrieve a user record.

    Returns None if user_id has
    no matching record.
    """
    ...

4. Missing the blank line between summary and body

The blank line separating the summary from the extended description is not optional. Automated indexing tools read exactly the first line as the summary. Without the blank line, the entire docstring is treated as one block — it gets truncated or mangled in generated documentation.

Why the blank line is a structural boundary, not whitespace

The blank line after the summary exists because multiple systems — Sphinx, help(), IDEs, and docstring indexers like pdoc — independently parse the summary line as a standalone unit. That first line is what shows up in module index listings, autocomplete pop-ups, and the one-sentence tooltip in VS Code and PyCharm. When there is no blank line, these tools have no clean stopping point. Sphinx's autodoc extension will render the first several sentences as a single collapsed summary in the module index — correct structure means only the first sentence appears there; the rest appears in the expanded entry.

This also affects the __doc__ attribute at runtime: the full docstring is stored as-is, so any code that parses obj.__doc__.split('\n\n')[0] to extract the summary (a common pattern in CLI tools and introspection utilities) will return the wrong result if the blank line is absent. Ruff rule D205 flags this violation specifically.

WRONG
def parse_config(path):
    """Parse the config file.
    Reads from path and returns
    a dict of key-value pairs.
    """
    ...
CORRECT
def parse_config(path):
    """Parse the config file.

    Reads from path and returns
    a dict of key-value pairs.
    """
    ...

5. Mixing style formats across a project

Switching between Google style and NumPy style — or any two formats — across different files breaks documentation generators and produces inconsistent IDE tooltips. Ruff can detect and flag this. Pick one format and configure it as your project's convention.

The downstream damage from mixed formats

Mixed formats cause silent failures, not obvious errors. Sphinx's napoleon extension parses Google-style and NumPy-style fields by pattern matching section headers. When it encounters a file that uses both, it will render the format it recognizes and silently ignore the sections it does not. A Parameters block from NumPy style inside a file otherwise written in Google style will not produce a warning — it will simply not appear in the generated HTML. Parameters that your API promises are documented will be invisible to anyone reading the hosted reference.

The deeper fix is not just locking in a convention in pyproject.toml — it is also migrating existing mixed files rather than leaving them. In a codebase with mixed formats, run ruff check --select D . --output-format json to get a machine-readable list of all violations, then sort by file to identify which modules have the most inconsistencies. Prioritize public API files (those imported in __init__.py) because those are what Sphinx renders and what consumers of your library actually read.

WRONG — mixed formats
# module_a.py — Google style
def foo(x):
    """Do something.

    Args:
        x (int): The input.
    """

# module_b.py — NumPy style
def bar(y):
    """Do something else.

    Parameters
    ----------
    y : int
        The input.
    """
CORRECT — lock it in via Ruff
# pyproject.toml
[tool.ruff.lint.pydocstyle]
convention = "google"

# Now both modules use Google style
def foo(x):
    """Do something.

    Args:
        x (int): The input.
    """

def bar(y):
    """Do something else.

    Args:
        y (int): The input.
    """

6. Repeating types when type hints are already present

When a function signature has type annotations, restating those types inside the docstring is redundant. The docstring should focus on behavior — what conditions trigger exceptions, what None means as a return value, what edge cases exist.

Why duplication creates a maintenance liability, not just noise

When types are stated in both the annotation and the docstring, they will drift. Someone refactors the function to accept str | Path instead of str, updates the annotation, and does not touch the docstring. Now the docstring actively lies to anyone who reads it. This is worse than no documentation at all, because the reader trusts the lie.

The correct approach is not to delete the type information from the docstring — it is to replace the type field with the behavioral information the type cannot express. Instead of price (float): The price, write price: Original price; must be non-negative. Behavior is undefined if NaN is passed. The constraint ("non-negative") and the edge case ("NaN") are things that float in a type hint can never tell you. That is what belongs in the docstring.

Configure napoleon_use_param = True and napoleon_use_rtype = True in your Sphinx conf.py, which tells the renderer to pull return types from annotations rather than docstring fields. This lets you omit type repetition in Google and NumPy style without losing the type information in the generated output — the best of both.

WRONG — redundant types
def discount(price: float,
             rate: float) -> float:
    """Apply a discount.

    Args:
        price (float): The price.
        rate (float): The rate.

    Returns:
        float: The discounted price.
    """
CORRECT — behavior focus
def discount(price: float,
             rate: float) -> float:
    """Return price after discount.

    Args:
        price: Original price,
            must be non-negative.
        rate: Decimal between 0 and 1.
            Raises ValueError otherwise.
    """

Key Takeaways

  1. Always use triple double quotes: Write """docstrings like this""", even for one-liners. This keeps the format consistent and makes future expansion easier.
  2. Write commands, not descriptions: Say "Return the value" rather than "Returns the value." This mirrors Python's own standard library style.
  3. One-liners stay on one line; multi-liners follow a clear structure: Summary line, blank line, extended body. The closing quotes go on their own line for anything multi-line.
  4. Document what matters for your context: Scripts need usage info, modules need export lists, functions need argument and return details, classes need method and variable overviews.
  5. Pick a style format and stick to it: PEP 257 gives you the structure; reST, Google, or NumPy style gives you the field conventions for parameters and returns. The choice matters less than consistency. Lock it in via Ruff's convention setting so your whole team stays aligned.
  6. Let tools do the heavy lifting: Docstrings are not just comments — they live in __doc__ and power everything from help() to Sphinx-generated sites. As of 2026, Ruff is the actively maintained tool for PEP 257 compliance checks; pydocstyle has been officially deprecated. Following PEP 257 means your documentation works wherever Python looks for it.

Docstrings are one of the smallest habits to build and one of the highest-value ones. Writing them well, following the conventions in PEP 257, makes your code easier to maintain, easier to share, and easier for others — and future you — to understand without having to read every line.