__init__ vs. Class Docstring: Where Does Your Documentation Go?

Two places in a Python class can legally hold a docstring: right under the class line, and right under def __init__. They are not interchangeable, and the style guides do not all agree on which one should hold constructor parameter documentation. Understanding what each source says — rather than what is commonly assumed — changes how you write and read Python documentation.

New Python programmers often place documentation wherever it seems to fit and move on. That works until someone calls help() on your Python class and finds half the information they expected is missing or buried in the wrong place. The distinction between a class docstring and an __init__ docstring is a small thing that has a visible effect — and the official guidance is more nuanced than many tutorials let on.

What each docstring actually documents

A class docstring describes the class itself: what it represents, what it is for, and how it behaves as a whole. An __init__ docstring, if it exists at all, describes the constructor method specifically — the parameters needed to create an instance.

Think of it this way. The class docstring answers the question "what is this thing?" The __init__ docstring answers "what do I pass in to build one?" Those are related questions, but they are not the same question.

class NetworkDevice:
    """Represents a managed network device in the inventory system.

    Tracks connection state, IP address, and device type. Use
    DeviceManager to query and update collections of devices.
    """

    def __init__(self, hostname: str, ip: str, device_type: str = "router"):
        self.hostname = hostname
        self.ip = ip
        self.device_type = device_type

In many cases, this is all you need. The class docstring describes the object; the constructor's parameters speak for themselves through type hints and clear names. You do not always need an __init__ docstring at all.

Note

A docstring is only a docstring when it is the first statement inside a block — a string literal with nothing before it except the def or class line itself. A string placed after any other statement is just an expression that does nothing.

What PEP 257 actually says

PEP 257 is Python's official docstring convention guide, authored by David Goodger and Ken Manheimer. It was adapted from Guido van Rossum's original Python style guide essay. Its guidance on class constructors is explicit and frequently misquoted.

"The class constructor should be documented in the docstring for its __init__ method." — PEP 257, authored by David Goodger and Ken Manheimer

This is the opposite of what many tutorials claim. PEP 257 routes constructor argument documentation to __init__, not to the class docstring. The class-level docstring has a separate purpose: PEP 257 says it should summarize the class's behavior and list public methods and instance variables. These are two different documentation jobs, and PEP 257 assigns them to two different docstrings.

Why this misquote persists

The confusion likely has two sources. First, NumPy-style projects put constructor params in the class docstring — that pattern is so visible in the scientific Python ecosystem that many developers assume it is the universal rule. Second, some older tutorials paraphrase PEP 257 inaccurately, dropping the word "__init__" and writing something like "class constructors are documented in the class docstring," which is the opposite of what the spec says.

PEP 257 also specifies that __init__ is a public method and, like all public methods, should have its own docstring. The class docstring is not a substitute for it. PEP 8, the general Python style guide, reinforces this: it states that docstrings are not necessary for non-public methods, which means the inverse — public methods including __init__ should have them. (PEP 8, peps.python.org)

# PEP 257 style — class summarizes behavior, __init__ documents parameters

class FirewallRule:
    """A single allow/deny rule applied to an interface.

    Tracks action, protocol, and port. Attach to an Interface
    object via interface.add_rule() to take effect.
    """

    def __init__(self, action: str, protocol: str, port: int):
        """Initialize the rule with action, protocol, and port.

        Args:
            action (str): Either 'allow' or 'deny'.
            protocol (str): Network protocol, e.g. 'tcp' or 'udp'.
            port (int): Destination port number.
        """
        self.action = action
        self.protocol = protocol
        self.port = port
Pro Tip

Tools like pydocstyle and flake8-docstrings can lint your docstrings against PEP 257 automatically. Run pydocstyle --convention=pep257 yourfile.py to check compliance — especially useful in team projects where consistency matters.

Pop Quiz Test your understanding

How help() and __doc__ behave

This is where placement has a concrete, observable effect. When you call help() on a class, Python displays ClassName.__doc__, which is the class-level docstring. It also shows each method's docstring, including __init__.__doc__. Both are shown, but the class docstring appears at the top and sets the tone for everything below it.

__doc__ Inspector — see what Python actually reads
ClassName.__doc__
ClassName.__init__.__doc__
help(ClassName) — top of output

Notice that accessing ClassName.__doc__ returns only the class-level string. The __init__ docstring is entirely separate and lives on the method object. If you put all your documentation inside __init__ and leave the class docstring blank, then MyClass.__doc__ returns None and any tool that reads the class description finds nothing.

Watch Out

Sphinx, pdoc, and similar documentation generators read __doc__ directly. A class with no class-level docstring produces a documentation page with no description, even if __init__ is thoroughly documented.

Google style and NumPy style compared

Here is where things diverge from both PEP 257 and from each other. Google style and NumPy style each take a meaningfully different approach, and neither works the way many tutorials describe.

Section 3.8.4 of the Google Python Style Guide is explicit about what each docstring holds. It specifies that classes should carry a docstring beneath the class definition, and that public attributes (excluding properties) belong in an Attributes section of that class docstring — which the guide describes as the place where "public attributes, excluding properties, should be documented." (Google Python Style Guide §3.8.4, google.github.io)

That same section then shows an __init__ docstring with an Args section for constructor parameters. The two docstrings coexist — they are not alternatives, they document separate things.

Style guide

The numpydoc specification makes its class-docstring-first approach explicit. On the topic of class documentation, the guide states:

"a docstring for the class constructor (__init__) can, optionally, be added" — numpydoc Style Guide, numpydoc.readthedocs.io

In other words, the numpydoc guide routes constructor parameters to the class docstring's Parameters section as the primary location, and explicitly marks any __init__ docstring as optional supplemental detail — only warranted when additional initialization context is needed beyond what the class docstring already covers.

This means the numpydoc approach is not "pick one and be consistent." It is: put constructor params in the class docstring's Parameters section, and only write an __init__ docstring if you need to document something beyond what is already there. The numpydoc validation tooling enforces this: a well-formed class docstring suppresses the GL08 warning that would otherwise flag a missing __init__ docstring.

__init__? Yes — required for public methods
__init__? Yes — holds Args; class docstring holds Attributes
__init__? Optional — used only when extra initialization detail is needed beyond the class docstring
__init__? Yes, but ClassName.__doc__ returns None
Source

When does __init__ not need a docstring at all?

The article has noted this once without explaining it, so it deserves a direct answer. An __init__ docstring adds real value when one or more of the following is true:

  • A parameter has non-obvious behavior — a default that changes under certain conditions, a constraint not expressed in the type hint, or a side effect on initialization.
  • The parameter name alone is ambiguous. timeout: int raises the question "timeout of what, in what unit?" A docstring resolves that. hostname: str does not raise that question.
  • Your project follows PEP 257 or Google style, both of which treat __init__ as a public method that always warrants its own docstring.
Decision map: should this __init__ have a docstring?
Is this a dataclass (@dataclass)?
YES
No __init__ to document Put everything in the class docstring. The decorator generates the constructor.
NO
Does your project use PEP 257 or Google style?
YES
Write the docstring __init__ is a public method. Document it.
NO (NumPy)
Are any params ambiguous, constrained, or non-obvious?
YES
Write the docstring Pick one location and be consistent.
NO
Skip it Clear names + type hints say enough. Don't repeat the signature.

When parameters are few, well-named, and accurately type-hinted, and when the class docstring already explains how to instantiate the object, a minimal or absent __init__ docstring is often cleaner than a redundant one. NumPy-style projects commonly operate this way by design. The decision is not laziness — it is a judgment that the code and type hints already communicate what a docstring would only repeat.

Pro Tip

If you are writing an __init__ docstring that only restates the parameter names and types already in the signature, delete it. Use that space to document the constraint, the unit, or the side effect — the thing the signature cannot tell you.

Where does a Raises section go?

Constructors raise exceptions. A ValueError when a parameter falls outside an accepted range, a TypeError when the wrong type is passed, a FileNotFoundError when a path argument does not resolve — these are real behaviors a caller needs to know about. The article has covered where Args and Parameters go. It has not answered where Raises goes, and the answer differs by style.

Under both PEP 257 and Google style, the Raises section belongs in the __init__ docstring, directly alongside Args. The logic is consistent with the rest of those conventions: constructor behavior — including failure modes — belongs in the constructor's own docstring. The class docstring describes what the class is; the __init__ docstring describes what can happen when you try to build one.

Under NumPy / numpydoc style, the Raises section goes in the class docstring, immediately after the Parameters section. Since the class docstring is already doing the work of documenting constructor parameters, it also carries the failure documentation. The numpydoc section ordering is: Summary, Extended summary, Parameters, Raises, Attributes, Notes — with Raises appearing before Attributes when both are present.

# Google style — Raises in __init__ docstring alongside Args

class PacketFilter:
    """Applies BPF-style filtering to captured network packets.

    Attributes:
        rule (str): The active filter expression.
        is_compiled (bool): True once the rule has been compiled.
    """

    def __init__(self, rule: str):
        """Initialize the filter with a BPF expression.

        Args:
            rule (str): A valid BPF filter expression, e.g. 'tcp port 443'.

        Raises:
            ValueError: If rule is an empty string.
            SyntaxError: If rule contains an invalid BPF expression.
        """
        if not rule.strip():
            raise ValueError("Filter rule cannot be empty.")
        self.rule = rule
        self.is_compiled = False

The NumPy equivalent puts both the Parameters and Raises sections in the class docstring, leaving __init__ with no docstring at all:

# NumPy style — Parameters and Raises both in class docstring

class PacketFilter:
    """Applies BPF-style filtering to captured network packets.

    Parameters
    ----------
    rule : str
        A valid BPF filter expression, e.g. 'tcp port 443'.

    Raises
    ------
    ValueError
        If rule is an empty string.
    SyntaxError
        If rule contains an invalid BPF expression.

    Attributes
    ----------
    is_compiled : bool
        True once the rule has been compiled.
    """

    def __init__(self, rule: str):
        if not rule.strip():
            raise ValueError("Filter rule cannot be empty.")
        self.rule = rule
        self.is_compiled = False
Pro Tip

Only document exceptions a caller can realistically handle or anticipate. An uncaught MemoryError during deep initialization does not belong in a Raises section. A ValueError you explicitly raise for an invalid argument does.

Classmethod alternative constructors

A common Python pattern is a class that exposes multiple construction paths through @classmethod factory methodsfrom_dict, from_file, from_env, and so on. These are fully public methods that accept parameters and return an instance. Where does their documentation go?

The answer is the same across all three conventions: each @classmethod constructor gets its own docstring, written directly under its def line, the same way any other public method would be documented. The class docstring describes the class as a whole. It does not need to enumerate every factory method and its parameters — that would duplicate information and create a maintenance problem. What belongs in the class docstring is a note that alternative constructors exist, so a reader knows to look.

class ThreatFeed:
    """A parsed collection of threat indicators from an external feed source.

    Use ThreatFeed(indicators) for direct construction or one of the
    class methods (from_stix, from_csv) to load from a file format.
    """

    def __init__(self, indicators: list):
        """Initialize the feed with a pre-parsed list of indicators.

        Args:
            indicators (list): A list of indicator dicts, each with
                'type', 'value', and 'confidence' keys.
        """
        self.indicators = indicators

    @classmethod
    def from_stix(cls, path: str) -> "ThreatFeed":
        """Load a threat feed from a STIX 2.1 JSON bundle file.

        Args:
            path (str): Filesystem path to the .json bundle.

        Returns:
            ThreatFeed: A new instance populated from the STIX bundle.

        Raises:
            FileNotFoundError: If path does not exist.
            ValueError: If the file is not a valid STIX 2.1 bundle.
        """
        # ... parsing logic ...
        return cls([])

    @classmethod
    def from_csv(cls, path: str, delimiter: str = ",") -> "ThreatFeed":
        """Load a threat feed from a CSV file.

        Args:
            path (str): Filesystem path to the .csv file.
            delimiter (str): Column separator. Defaults to ','.

        Returns:
            ThreatFeed: A new instance populated from the CSV rows.

        Raises:
            FileNotFoundError: If path does not exist.
        """
        # ... parsing logic ...
        return cls([])

Notice that the class docstring mentions from_stix and from_csv exist, but does not repeat their parameter lists. That information lives where tools will find it: on each method's own docstring. Sphinx's autoclass directive renders class and method docstrings together, so the reader gets the full picture without duplication in the source.

Note

The Returns section is relevant here even though a constructor normally returns None. Because a @classmethod factory method explicitly returns an instance, documenting the return type is not optional. Both Google and NumPy styles have a Returns section for exactly this situation.

Three cases where the standard advice breaks down

PEP 257 and the style guides assume a normal class with a standard __init__. Three patterns come up regularly in Python code where that assumption does not hold, and the usual guidance either becomes ambiguous or produces a result you probably did not intend.

Dataclasses

The @dataclass decorator generates __init__ automatically from the class-level field definitions. There is no def __init__ for you to write a docstring on. The generated method does have a __doc__ attribute, but it is set by the decorator and not something you can annotate directly without overriding the constructor entirely — which defeats the point of using a dataclass.

The practical answer for dataclasses is to document everything in the class docstring. Field descriptions go there, constructor intent goes there, and the class docstring carries the entire load. Tools like Sphinx with the autodoc extension can render field-level documentation from type annotations and inline comments, so the class docstring does not need to repeat every field name — it describes the object's purpose and notes anything the field definitions do not make obvious.

from dataclasses import dataclass, field

@dataclass  Generates __init__ — no location to add a constructor docstring
class ThreatIndicator:
    """A single indicator of compromise attached to a threat report.

    All fields are required except tags, which defaults to an empty list.
    The ioc_value should be normalized to lowercase before assignment;
    matching logic elsewhere is case-sensitive.
    """
    ioc_type: str       # e.g. "ip", "domain", "file_hash"
    ioc_value: str
    confidence: int     # 0–100 scale
    tags: list[str] = field(default_factory=list)

There is no __init__ docstring to write here. The class docstring is the only place to put documentation that affects how someone instantiates the object.

One wrinkle: if the dataclass defines a __post_init__ method to handle validation or computed fields after the generated __init__ runs, that method should have its own docstring. __post_init__ is a named, callable method that a developer may encounter directly — in debugging output, in a subclass, or when overriding it. Unlike __init__, it is not generated automatically. There is a real def line to attach a docstring to, and if it raises exceptions or performs non-obvious transformations, those behaviors belong in that docstring.

from dataclasses import dataclass, field

@dataclass
class ThreatIndicator:
    """A single indicator of compromise attached to a threat report.

    All fields are required except tags, which defaults to an empty list.
    ioc_value is normalized to lowercase on initialization.
    """
    ioc_type: str
    ioc_value: str
    confidence: int
    tags: list[str] = field(default_factory=list)

    def __post_init__(self):
        """Normalize fields and validate after __init__ runs.

        Raises:
            ValueError: If confidence is outside the 0–100 range.
        """
        self.ioc_value = self.ioc_value.lower()
        if not 0 <= self.confidence <= 100:
            raise ValueError(f"confidence must be 0–100, got {self.confidence}")

When a subclass inherits __init__ without overriding it, SubClass.__init__.__doc__ returns the parent's docstring — or None if the parent had none. That can mislead a caller who reads the subclass documentation and finds constructor parameters that belong to a different class context.

The cleaner approach is to write a class docstring on the subclass that describes what makes it distinct, and to note that constructor parameters are inherited. Sphinx's :inherited-members: flag handles the generated documentation side automatically.

class BaseScanner:
    """Base class for network scanning operations."""

    def __init__(self, target: str, timeout: int = 10):
        """Initialize the scanner against a target.

        Args:
            target (str): Hostname or CIDR range to scan.
            timeout (int): Per-host timeout in seconds. Defaults to 10.
        """
        self.target = target
        self.timeout = timeout


class PortScanner(BaseScanner):
    """Scanner that checks open TCP ports on a target host.

    Inherits constructor from BaseScanner. Use the ports parameter
    on scan() to restrict the range checked.
    """
    # No __init__ override — BaseScanner.__init__.__doc__ is inherited.
    # The class docstring describes what is distinct about this subclass.

Classes that use __new__ instead of __init__

Some classes control instantiation through __new__ rather than __init__ — singletons, immutable types, and factory patterns that need to return a different type are common examples. PEP 257 is silent on __new__. The docstring conventions for __init__ do not transfer automatically.

The working convention is to treat __new__ like any other public method: give it its own docstring that documents the parameters it accepts, and give the class a docstring that explains the instantiation pattern. If __new__ is doing work that callers need to understand — such as enforcing a singleton or returning a cached instance — that belongs in the class docstring, not buried in __init__.

class ConfigManager:
    """Singleton that holds the active application configuration.

    Only one instance exists per process. Subsequent calls to
    ConfigManager() return the same object without re-reading the
    config file.
    """

    _instance = None

    def __new__(cls, config_path: str = "/etc/app/config.yaml"):
        """Return the existing instance or create one from config_path.

        Args:
            config_path (str): Path to the YAML configuration file.
                Only used on first instantiation.
        """
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._load(config_path)
        return cls._instance
Spot the Bug One of the lines below breaks the rules you just learned

The AuthToken class below is trying to follow Google style, but one decision is wrong — a real mistake that would cause a documentation tool to miss important information. Click the line you think contains the bug.

class AuthToken: def __init__(self, token: str, ttl: int = 3600): """Manages a short-lived bearer token for API authentication. Args: token (str): Raw bearer token string from the auth server. ttl (int): Seconds until the token expires. Defaults to 3600. Attributes: is_expired (bool): True when current time has passed ttl. """ self.token = token self.ttl = ttl self.is_expired = False

Hint: think about where Google style says Attributes belong.

Key Takeaways

  1. Always write the class docstring: ClassName.__doc__ is what documentation tools display at the top of your class page. A class with no class-level docstring produces a documentation page with no description, even if __init__ is thoroughly documented.
  2. PEP 257 puts constructor args in __init__: The spec is explicit on this point — constructor parameter documentation belongs in the __init__ docstring, not in the class-level docstring. (peps.python.org/pep-0257) The class docstring should summarize behavior and list public methods and instance variables.
  3. Google style uses both, for different things: The class docstring holds Attributes; __init__ holds Args. These are not duplicates — they document different aspects of the class.
  4. NumPy / numpydoc style uses the class docstring as the primary location: The numpydoc style guide is explicit — the class docstring's Parameters section documents constructor parameters. An __init__ docstring is described as optional, used only when additional initialization detail is needed. This is different from "pick one and be consistent" — the class docstring approach is the stated default. (numpydoc.readthedocs.io)
  5. They are separate objects: MyClass.__doc__ and MyClass.__init__.__doc__ are independent strings. Filling one does not fill the other. This is the concrete mechanical reason why placement decisions have real consequences in the terminal and in generated documentation.
  6. Type hints reduce docstring pressure: When parameters have descriptive names and accurate type annotations, you often need less text in the docstring. Let annotations carry the type information; use the docstring to explain intent and behavior — especially non-obvious constraints, defaults, and side effects.
  7. A Raises section follows the same placement rule as Args: Under PEP 257 and Google style, constructor exception documentation belongs in the __init__ docstring. Under NumPy style, it belongs in the class docstring's Raises section, which appears after Parameters and before Attributes. Only document exceptions a caller can anticipate and handle — not every possible internal failure.
  8. Classmethod alternative constructors get their own docstrings: Factory methods like from_file or from_dict are public methods and should be documented on their own def line with Args, Returns, and Raises sections as appropriate. The class docstring can mention they exist, but should not repeat their parameter lists.
  9. Dataclasses have no writable __init__ docstring: The constructor is generated. Document everything in the class docstring. This is not a workaround — it is the correct pattern for the tool. If the class defines __post_init__, that method is handwritten and should have its own docstring, especially if it raises or transforms data.
  10. Subclasses that do not override __init__ inherit the parent's docstring: Write a class docstring on the subclass that describes what is distinct. Do not duplicate the parent's constructor documentation — that creates maintenance risk with no readability benefit.
  11. Classes using __new__ require deliberate documentation: PEP 257 does not address __new__. Treat it as a public method with its own docstring, and use the class docstring to describe the instantiation pattern — especially if the class is a singleton or returns a cached instance.

The class docstring is the front door. It is the first thing a developer sees when they call help() or browse generated documentation. The __init__ docstring is the instruction manual for building an instance — when it exists. PEP 257 treats them as separate responsibilities. Google style does the same, and both conventions also route Raises to the __init__ docstring when the constructor can fail. NumPy / numpydoc style assigns the primary constructor documentation — including Parameters and Raises — to the class docstring's named sections, with __init__ docstrings available as optional supplemental detail. For @classmethod alternative constructors, the rule is consistent across all three: each factory method is a public method and gets its own docstring. And when the class does not follow the standard pattern — a dataclass, a subclass that inherits its constructor, a singleton using __new__ — the underlying principle still applies: document what the code cannot say for itself, put it where tools can find it, and do not repeat what is already visible.

How to choose where your constructor documentation goes

The decision comes down to three questions. Answer them in order and placement becomes mechanical rather than a judgment call.

  1. Is this a @dataclass? If yes, there is no hand-written __init__ to document. Put everything — including the field descriptions that would otherwise go in Args — in the class docstring. If the class defines __post_init__, give that method its own docstring.
  2. What convention does your project follow? Check existing class docstrings, your linter configuration, or team style guide. The answer will be one of: PEP 257 / Google style (constructor args go in __init__) or NumPy / numpydoc style (constructor args go in the class docstring's Parameters section).
  3. Does your __init__ do anything non-obvious? Even under NumPy style, if the constructor has constrained parameters, units-sensitive defaults, or side effects that the signature cannot express, an __init__ docstring adds genuine value. Under PEP 257 and Google style, write one regardless — __init__ is a public method and always warrants its own docstring.

Once placement is decided, always write the class docstring first. It is the entry point for help(), Sphinx, and every other documentation tool. The __init__ docstring layers detail on top — it does not replace what belongs at the class level.

Quick reference

PEP 257 / Google style: class docstring for overview and Attributes; __init__ docstring for Args and Raises. NumPy style: class docstring for overview, Parameters, Raises, and Attributes; __init__ docstring is optional. All styles: each @classmethod factory method gets its own docstring.

Frequently asked questions

Does PEP 257 say to put constructor arguments in the class docstring?

No. PEP 257 explicitly routes constructor argument documentation to __init__, not to the class-level docstring. The spec treats __init__ as a public method whose documentation belongs in its own docstring. The class docstring serves a different purpose: summarizing the class's behavior and listing its public methods and instance variables. The two docstrings are complementary, not interchangeable.

Where does NumPy style put constructor parameters?

In the class docstring's Parameters section. This is the default and primary location under the numpydoc convention. An __init__ docstring is explicitly described as optional — warranted only when additional initialization detail is needed beyond what the class docstring already covers. This is a meaningful difference from PEP 257 and Google style, and numpydoc's validation tooling enforces it: a complete class docstring suppresses the GL08 warning that would otherwise flag a missing __init__ docstring.

What does MyClass.__doc__ return vs MyClass.__init__.__doc__?

MyClass.__doc__ returns the string placed immediately after the class definition line. MyClass.__init__.__doc__ returns the string placed immediately after def __init__. They are entirely separate objects. Filling one does not fill the other. If you place all your documentation inside __init__ and leave the class-level string blank, then MyClass.__doc__ returns None and any tool that reads the class description finds nothing.

Does Google style put constructor args in the class docstring?

Not exclusively. The Google Python Style Guide shows a class docstring with an Attributes section, alongside a separate __init__ docstring with an Args section. The two docstrings coexist and serve different purposes — one describes what the class is, the other describes what you pass to build an instance of it.

When should I write an __init__ docstring?

Under PEP 257 and Google style, always — __init__ is a public method and public methods should have docstrings. Under NumPy style, optionally — only when additional initialization detail is needed beyond what the class docstring's Parameters section already covers. Under any convention, a well-named parameter with an accurate type hint sometimes communicates more cleanly than a docstring that merely restates the signature.

Where does the Raises section go — class docstring or __init__?

It follows the same placement rule as your parameter documentation. Under PEP 257 and Google style, Raises belongs in the __init__ docstring alongside Args — constructor failure modes are part of constructor behavior. Under NumPy / numpydoc style, Raises belongs in the class docstring's named Raises section, which appears after Parameters and before Attributes. Only document exceptions a caller can realistically anticipate and handle.

How do I document @classmethod alternative constructors like from_dict or from_file?

Each @classmethod constructor is a public method and should carry its own docstring written directly under its def line, with Args, Returns, and Raises sections as needed. The class docstring can note that alternative constructors exist, but should not duplicate their parameter documentation. This approach is consistent across PEP 257, Google style, and NumPy style.