The Documentation Is Part of the Object
Hatched by Kai Nguyen
Aug 30, 2026
11 min read
2 views
93%
What if the most important behavior of a Python object is not something it does, but something it promises?
A class can create users, invoices, sensors, or game characters. Its methods can calculate totals, send messages, or change internal state. Yet none of that tells another programmer what the object means, when it should be used, or what will remain true after it is called. Code can execute perfectly and still fail as a shared system because its meaning is invisible.
This reveals a deeper connection between object design and documentation: an object is not merely a bundle of data and behavior. It is a bundle of expectations. The class defines a possible shape. The instance carries actual circumstances. The methods govern permitted change. Documentation makes the invisible expectations legible.
The practical consequence is larger than writing better comments. Good documentation does not sit beside design as decoration. It completes the design by turning implementation into a usable contract.
A Class Is a Blueprint, but a Blueprint Is Not a Building
A class is often introduced as a blueprint. That analogy is useful, but incomplete. A blueprint describes the structure of a building before the building exists. A class does something similar: it specifies attributes and methods that instances will possess. The instance is the realized object, containing actual data.
Consider a simple account:
class Account:
interest_rate = 0.02
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
def deposit(self, amount):
self.balance += amount
The class attribute interest_rate expresses a value shared by every account. The instance attributes owner and balance express what varies from one account to another. The method deposit expresses a permitted transition from one state to another.
But the code leaves crucial questions unanswered. Can the balance be negative? Must the amount be positive? Is the interest rate guaranteed to be uniform, or can an individual account override it? Does deposit return anything meaningful? Is the balance measured in dollars, cents, or some other unit?
The interpreter does not need answers to these questions. The people using the class do.
This is where the blueprint analogy breaks down. A physical blueprint is usually interpreted inside a stable professional context. A Python class is interpreted by future programmers who may not know why an attribute exists, which states are valid, or which details are safe to change. The source code shows the machinery, but not always the intention.
A class therefore has two structures:
- Its mechanical structure, which consists of attributes, methods, inheritance, and executable logic.
- Its semantic structure, which consists of purpose, assumptions, guarantees, and boundaries.
The first can be inspected by a computer. The second must be communicated to humans. Docstrings are one of the primary places where that communication happens.
A class without documentation may still have behavior, but its behavior has no reliable audience.
The Real Unit of Design Is the Promise
Object oriented design is often described as bundling properties and behaviors together. That is a useful organizational principle, but bundling alone does not produce a good abstraction. A badly designed object can bundle confusion just as efficiently as it bundles clarity.
The more powerful question is not, What data and methods belong together? It is, What promise should this object make to the rest of the program?
Imagine a Temperature object. Its internal value might be stored in Celsius, Fahrenheit, or Kelvin. Its methods might convert units, compare readings, or report whether a threshold has been crossed. Users of the object should not need to know its internal representation. They need to know what a value means and what each operation guarantees.
class Temperature:
"""Represent an air temperature in degrees Celsius."""
def __init__(self, celsius):
"""Store a temperature measured in degrees Celsius."""
self.celsius = celsius
def in_fahrenheit(self):
"""Return the temperature converted to degrees Fahrenheit."""
return self.celsius * 9 / 5 + 32
The docstrings do more than explain English vocabulary. They establish a coordinate system. Without them, a reader might reasonably wonder whether celsius is an input unit, an output unit, or merely an internal name. The documentation narrows interpretation before a mistake becomes code.
This suggests a useful model:
An object has a state space, a set of operations, and a contract.
The state space is the collection of conditions the object can occupy. For an account, it might include owner and balance. For a temperature, it includes a numerical reading. The operations are the methods that can inspect or change that state. The contract explains which states and transitions are valid.
Documentation should make all three visible. A class docstring describes the identity of the object. An attribute docstring can clarify the meaning of a particular piece of state. A method docstring explains an operation and, when necessary, its inputs, output, side effects, and constraints.
The shortest documentation is appropriate when the meaning is obvious. A one line description can be excellent for a simple method such as in_fahrenheit. But brevity is not a virtue when it hides a decision. The test is not whether a docstring is short. The test is whether a competent reader can predict how to use the object correctly.
Mutation Turns Documentation Into Safety Equipment
Python objects are mutable by default. This means that an instance can change after it is created, often through an instance method or direct assignment to an attribute. Mutation is useful because it lets an object represent a process over time. It is also dangerous because a method can alter the future behavior of the object without making that alteration obvious.
Return to the account example. A caller may assume that deposit(100) simply records an additional amount. But the actual contract could be more complicated. It might reject negative values, apply a fee, trigger an event, write to a database, or return the new balance. The method name suggests less than the method might do.
This is why documentation becomes more important as mutability increases. A purely mathematical function can often be understood by its input and output. A mutable object requires a reader to track history. The same method call can produce different results depending on what happened earlier.
We can describe this as the history burden of an object. The more its behavior depends on prior mutations, the more its documentation must explain state transitions.
A useful method docstring might look like this:
class Account:
"""Represent an account with a nonnegative balance."""
def deposit(self, amount):
"""Add a positive amount to the balance.
The balance is updated in place. Return the new balance.
Raise ValueError when amount is zero or negative.
"""
if amount <= 0:
raise ValueError("amount must be positive")
self.balance += amount
return self.balance
The description does not repeat the syntax. It identifies the important consequences: mutation happens, a condition must hold, an error is possible, and a value is returned. These are the details that let another programmer reason about the object without reading every implementation branch.
Documentation is especially valuable at the boundary between public and private knowledge. A method may use ten internal steps, but users need to know the stable outcome. Conversely, an attribute may look public simply because Python allows access to it, while the design intends it to be treated as read only.
The distinction is not enforced by syntax alone. It is established through naming, structure, and clear communication. A documented contract tells users which details they may rely on and which details they should ignore.
Inheritance Multiplies Meaning, Not Just Code
Inheritance introduces another layer of difficulty. A child class takes on attributes and methods from a parent class, and it can override or extend them. This is often presented as a way to reuse code. But inheritance also reuses expectations, whether the programmer notices or not.
Suppose there is a general Notification class with a send method. An EmailNotification subclass may inherit that method, while an UrgentEmailNotification overrides it to add priority handling. The child inherits more than executable instructions. It inherits the conceptual question: what does it mean for a notification to be sent?
If the parent documentation says that send queues a message for delivery, a child that sends immediately has changed the contract, even if the method signature remains the same. If the parent says that the method returns a delivery identifier, a child returning None has created a semantic incompatibility. The code may look polymorphic while the meaning is not.
This is why inheritance should be treated as contract inheritance. Before extending a class, ask four questions:
- Which attributes and methods are being inherited?
- Which guarantees must remain true?
- Which behaviors may be extended safely?
- Which inherited assumptions no longer apply?
Docstrings provide a place to answer these questions. A child class can explain what it adds, what it changes, and what it preserves. Additional documentation is not redundant when the subclass alters context. It is a record of the new contract.
There is also a danger in using inheritance to represent superficial similarity. Two things may both have a send method while differing in timing, failure behavior, or side effects. Shared vocabulary does not prove shared abstraction. The deeper the inheritance hierarchy, the more carefully documentation must distinguish genuine substitutability from accidental code reuse.
Inheritance is not free reuse. It is the transfer of assumptions from one object to another.
That is a demanding standard, but it explains why seemingly minor changes in a parent class can affect distant parts of a system. A parent class is not merely a source of methods. It is a source of promises that many child classes may silently depend on.
A Practical Framework for Writing Objects People Can Trust
The most useful documentation process begins before the method body is written. For each public class, attribute, and method, define the object through five questions.
1. Identity
What is this object for? Complete the sentence: This object represents ___. If the answer requires a paragraph of implementation detail, the abstraction may not be clear yet.
2. State
Which data does each instance carry? Distinguish values that vary by instance from values shared by the class. State units, valid ranges, defaults, and whether callers are allowed to change the value directly.
3. Transition
What does each method do to the state? Say whether it mutates the instance, creates a new object, or merely observes current data. Mutation should never be an accidental discovery.
4. Guarantee
What can callers rely on after the operation? Describe return values, ordering, persistence, exceptions, and important invariants. A guarantee is more useful than a description of internal steps because it survives refactoring.
5. Relationship
If the class inherits from another class, what does it preserve and what does it change? If it collaborates with other objects, what does it expect from them? Objects rarely live alone, so their documentation should expose meaningful relationships.
This framework also gives documentation a natural shape. A concise summary line identifies the object or operation. A blank line can then separate that summary from a fuller explanation of constraints and consequences. The format matters less than the discipline behind it: begin with the central meaning, then provide the detail required for correct use.
Attribute docstrings and additional docstrings are particularly useful when meaning does not fit beside a declaration. They can explain class level policies, instance state, or a group of related operations. The goal is not to annotate every obvious line. The goal is to preserve decisions that would otherwise disappear into the implementation.
A strong review question is this: Could someone replace the implementation while preserving the documentation? If yes, the documentation is probably describing a contract. If no, it may be merely narrating the current code.
That distinction matters because implementations change. A list may become a dictionary. A local calculation may move to a service. A subclass may be replaced by composition. Documentation that describes the promise remains useful through these changes. Documentation that describes the machinery becomes obsolete as soon as the machinery improves.
Key Takeaways
- Design classes around promises, not just bundles of data and methods. Define what the object represents and what users may safely expect.
- Document state transitions. For mutable objects, explain what changes, what remains unchanged, what is returned, and which errors are possible.
- Separate shared policy from individual state. Class attributes communicate common values, while instance attributes communicate variation. Document the difference when confusion is likely.
- Treat inheritance as contract inheritance. A subclass should preserve inherited guarantees or clearly document the change.
- Write documentation for replacement, not inspection. Describe stable meaning and observable behavior rather than implementation steps that may soon change.
The best software abstractions do not force readers to reconstruct intention from syntax. They let readers understand the intended world first, then inspect the code only when necessary. That is the real relationship between object design and documentation: one gives a system shape, while the other gives that shape a shared interpretation.
A class is called a blueprint because it comes before the object. But documentation performs an equally important act: it comes before the reader's decision to trust the object. When code is shared, inherited, mutated, and revised, that trust is not a soft benefit. It is part of the architecture.
The question is therefore not whether your classes have docstrings. The question is whether your objects have promises clear enough to survive contact with another mind.
Sources
Hatch New Ideas with Glasp AI 🐣
Glasp AI allows you to hatch new ideas based on your curated content. Let's curate and create with Glasp AI :)
Start Hatching 🐣