Skip to content
VLSI Mentor

Python Part I · Module 11 · Functions

Docstrings: Saying What a Function Promises

A function's header says what it takes; its body says what it does. Neither says what it promises. A reader deciding whether to call count_errors wants one sentence — what comes back, and under what conditions — and would rather not reconstruct it from a loop. Python has a dedicated place for that sentence, and it is worth knowing that it is not a comment. A comment is discarded; a docstring is kept, attached to the function, and readable while the program runs. That difference is why typing help() at something has been producing useful answers since Module 1, and why the one-line habit this chapter recommends costs so little.

Foundation14 min readPythonPart IFundamentalsFunctionsDocumentation

Part I · Module 11 · Chapter 11.11 · Docstrings

1. The Question a Caller Actually Has

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def count_errors(text_lines):
    found = 0
    for line in text_lines:
        if "UVM_ERROR" in line:
            found += 1
    return found

To decide whether to call that, you need to know: does it count UVM_FATAL too? Does it return a number or a list? What does it do with an empty input?

Two of those three are answerable by reading the body, and the third requires reasoning about it. The header answers none of them.

2. One Line at the Top

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def count_errors(text_lines):
    """Return the number of lines containing UVM_ERROR."""
    found = 0
    for line in text_lines:
        if "UVM_ERROR" in line:
            found += 1
    return found


print(count_errors(["UVM_ERROR @1: a", "UVM_FATAL @2: b"]))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1

A string literal as the first statement of the body is a docstring. It is written with triple quotes by convention — Chapter 4.2's """...""" — even when it fits on one line, because that is what every reader expects to see.

The sentence answers the caller's question: UVM_ERROR only, a number back. The 1 from a two-line input containing one error and one fatal confirms it.

3. It Is a Value, Not a Comment

This is the difference worth knowing. A comment is removed before the program runs; a docstring is kept and attached to the function:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def count_errors(text_lines):
    """Return the number of lines containing UVM_ERROR."""
    return 0


print(count_errors.__doc__)
print(type(count_errors.__doc__))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Return the number of lines containing UVM_ERROR.
<class 'str'>

__doc__ is an ordinary attribute holding an ordinary string. Nothing in a # comment is reachable that way.

A function without one has __doc__ of None — not an empty string:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def plain():
    return 1


print(plain.__doc__)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
None

4. This Is Where help() Gets Its Text

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def count_errors(text_lines):
    """Return the number of lines containing UVM_ERROR."""
    return 0


help(count_errors)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Help on function count_errors in module __main__:

count_errors(text_lines)
    Return the number of lines containing UVM_ERROR.

help() prints the signature and the docstring. That is all it has — every helpful help() output you have ever seen was somebody writing one of these.

5. It Must Be the First Statement

The placement rule is strict, and breaking it fails silently:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
def count_errors(text_lines):
    found = 0
    """Return the number of lines containing UVM_ERROR."""
    return found


print(count_errors.__doc__)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
None

The triple-quoted string is still in the body. Python evaluated it, found nothing to do with it, and discarded it — so the function has no docstring at all and nothing warned you.

Only the first statement of the body counts. A blank line above it is fine; a line of code above it is not.

6. Say What It Promises, Not How It Works

The placement is syntax; this is the part that decides whether the line is worth writing.

Weak — a paraphrase of the body:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
"""Loop over the lines and count the ones with UVM_ERROR in them."""

A reader can see the loop. This adds nothing, and it goes stale the moment the implementation changes — at which point it is actively misleading.

Strong — a contract:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
"""Return the number of lines containing UVM_ERROR."""

What comes back, stated from the caller's point of view. If the body is rewritten to scan differently, this stays true.

Three things a good docstring covers, in order of importance:

  1. What it returns, phrased as Return .... This is the one that matters.
  2. What the arguments mean, when the names are not self-explanatory.
  3. Anything surprising — an edge case, a mutation, a limit.

The third is where docstrings earn their place:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def drain(queue):
    """Return every job from queue, newest first, and leave queue empty."""
    processed = []
    while queue:
        processed.append(queue.pop())
    return processed


jobs = ["j1", "j2", "j3"]
done = drain(jobs)
print(done)
print(jobs)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
['j3', 'j2', 'j1']
[]

and leave queue empty is the sentence that stops someone using jobs afterwards. Chapter 11.7 Section 8 said a mutating function must announce the change in its name; the docstring is where the detail goes.

7. The Multi-Line Form

When one line is not enough, the convention is a summary line, a blank line, then the detail:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def parse(line, strict=False):
    """Split one log line into a severity and a message.

    line   -- a single log line, leading whitespace ignored
    strict -- when True, an unrecognised severity returns "unknown"
              rather than "info"

    Returns a (severity, message) tuple.
    """
    return ("info", line.strip())


print(parse("  UVM_INFO @1: ready  "))
print(parse.__doc__.strip().split("\n")[0])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
('info', 'UVM_INFO @1: ready')
Split one log line into a severity and a message.

The summary line stays readable on its own — tools and readers both take the first line as the short description, which is why it must make sense without the rest.

Keep the closing """ on its own line for anything multi-line. It is the convention, and it keeps the indentation obvious.

8. A Docstring Is a Promise, Not a Guarantee

Nothing checks it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def count_errors(text_lines):
    """Return the number of lines containing UVM_ERROR."""
    return "seventeen"


print(count_errors(["UVM_ERROR @1: a"]))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
seventeen

The docstring says it returns a number. It returns a string. Python has no opinion.

That is worth stating plainly because it sets the expectation for the next chapter: type hints have exactly the same property. Both are documentation that the language stores and does not enforce. Their value is entirely in what they tell a human and a tool — which is considerable, and is not the same as a check.

The practical consequence: a wrong docstring is worse than none, because a reader will believe it. Update it when you change what a function returns.

9. When to Write One

Worth the line:

  • any function another person will call
  • any function whose return value is not obvious from its name
  • any function with a surprise — mutation, an edge case, a non-obvious empty-input behaviour

Not worth it:

  • a three-line helper whose name already says everything (def is_error(line): returning a bool)
  • a function where the docstring would only restate the signature

The test is whether the sentence tells a caller something the header does not. If """Return the test name.""" sits above def test_name(result):, delete it.

10. Predict, Run, Observe

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# A
def a():
    """Doc A."""
    return 1
print("A:", a.__doc__)

# B
def b():
    return 1
print("B:", b.__doc__)

# C
def c():
    x = 1
    """Doc C."""
    return x
print("C:", c.__doc__)

# D
def d():
    """Doc D."""
    return 1
print("D:", len(d.__doc__), type(d.__doc__))

The verified output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
A: Doc A.
B: None
C: None
D: 6 <class 'str'>

A — the first statement, so it is the docstring.

B — no docstring at all gives None.

C — None, because a line of code came first. The string is still in the body and was simply thrown away.

D — an ordinary string of six characters, counting the full stop.

11. Exercises

Exercise 1 — write the contract. Add a one-line docstring to each: (a) a function returning the first failing test name or "" (b) a function returning a dict of severity counts (c) a function that empties a queue and returns what it took.

Exercise 2 — reach it at run time. Write a function with a docstring, print __doc__, then print help() of it. Say which of the two you would use at an interpreter and why.

Exercise 3 — break the placement. Put a statement above a docstring and print __doc__. Explain in two sentences what happened to the string and why nothing warned you.

Exercise 4 — contract or paraphrase. For each, say which it is and rewrite the weak ones: (a) """Return True when the log has no errors.""" (b) """Use a for loop to go through the list.""" (c) """Return the slowest test as a (name, seconds) tuple.""" (d) """Helper function."""

Exercise 5 — the surprise. Write a function that sorts its argument in place and returns nothing. Write the docstring that would stop a caller being surprised, then say which word in your function's name should also carry that warning.

Exercise 6 — multi-line. Write a multi-line docstring for a function taking three arguments, with a summary line, argument notes and a statement of what it returns. Print only the summary line.

Exercise 7 — wrong on purpose. Write a function whose docstring claims it returns a count and which actually returns a list. Run it and show nothing complains. Say in one sentence why this is worse than having no docstring.

Exercise 8 — delete some. Find three functions you have written in this module whose docstrings would only restate the signature, and say why leaving them out is the better choice.

12. Summary

  • A docstring is a string literal as the first statement of a function body, written with triple quotes by convention.
  • It is a value, not a comment. It is attached to the function and readable at run time through __doc__.
  • A function without one has __doc__ of None, not an empty string.
  • help() prints the signature and the docstring — that is where every helpful help() output comes from.
  • It must be the first statement. One line of code above it and the string is silently discarded.
  • Say what it returns, not how it works. A paraphrase of the body adds nothing and goes stale.
  • Cover the surprises — mutation, edge cases, non-obvious empty-input behaviour.
  • The multi-line form is a summary line, a blank line, then detail, with the summary readable on its own.
  • Nothing enforces a docstring. A wrong one is worse than none, because a reader will believe it.
  • Skip it when the name already says everything.

The next chapter writes the other half of the contract — and has the same honest limit.

Where this fits

Part of the Python curriculum.