Python Module 4 – Functions, scope, closures and decorators
Docstrings, comments and PEP 8 style
Write docstrings that help() and editors show, follow PEP 8 naming and layout, and tell a comment that explains why from one that only repeats the code.
What you will learn
- Write useful docstrings for modules, functions and classes, with a PEP 257 summary line
- Check the examples in a docstring with doctest
- Apply PEP 8 naming and layout conventions
- Decide when a comment helps and when it hurts
- Choose between a formatter and a linter for a style problem
Before you start
What you’ll learn · Before you start
What you will learn
- Write useful docstrings for modules, functions and classes, with a PEP 257 summary line
- Check the examples in a docstring with doctest
- Apply PEP 8 naming and layout conventions
- Decide when a comment helps and when it hurts
- Choose between a formatter and a linter for a style problem
Before you start
On this page
A teammate types help(monthly_payment) and gets back only the signature, although the author wrote two careful
comments inside the function. Comments are for people reading the source, and Python throws them away; what help()
shows is the docstring: A string written as the first statement of a module, class or function. Python keeps it as the object's __doc__ attribute, which help(), editors and doctest read., a string written as the first statement of a module, a function or
a class, which Python keeps with the object as its __doc__ attribute, so that help(), editors and documentation
tools can show it to anyone who uses your code. PEP 8: Python's style guide: the naming, layout and comment conventions most Python code follows, such as snake_case names for functions, four-space indents and lines of at most 79 characters. is Python’s style guide: the conventions
most Python code follows, such as four-space indentation, snake_case: Writing a name in lower case with underscores between its words, such as shipping_cost. PEP 8 uses it for functions, variables and parameters. names for functions
and variables, CapWords names for classes and lines of at most 79 characters.
Below you can follow a docstring from the source to help(), see what PEP 257 asks of its first line, let
doctest: The standard library module that finds examples written after >>> in docstrings, runs them and reports each one whose output differs from the line below it. check its examples, clean up a function to PEP 8, and tell a comment that earns its
place from one that only repeats the code.
A docstring is kept with the function; a comment is not
def km_to_miles(km):
"""Convert a distance in kilometres to miles."""
return km / 1.609344
print(round(km_to_miles(42.195), 2))
print(km_to_miles.__doc__)
help(km_to_miles) Output
26.22
Convert a distance in kilometres to miles.
Help on function km_to_miles in module __main__:
km_to_miles(km)
Convert a distance in kilometres to miles.
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 docstring_help.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The string under the def line is not just a remark. Python stores it as km_to_miles.__doc__, and help() prints it
under the function’s signature, as editors do when you point at the function or start typing a call. A comment in the
same place would be invisible to all of them:
Where a docstring and a comment end up
Text description of the diagram
The diagram starts from a source file that contains a docstring and a comment, and follows each one.
- The docstring is kept with the function as its `__doc__` attribute. From there, `help()`, editors, `doctest` and documentation tools can read it, so people who use the function see it.
- The comment is dropped when Python reads the file. Only people who read the source code see it.
So the two have different readers. A docstring tells someone who uses a function what it does and how to call it. A comment tells someone who changes the code something the code cannot say for itself.
help() has one fallback worth knowing. For a function without a docstring, it shows the comment lines directly above
the def, which it reads from the source file:
# Convert a distance in kilometres to miles.
def km_to_miles(km):
return km / 1.609344
print("__doc__ is", km_to_miles.__doc__)
help(km_to_miles) # no docstring: help() shows the comment above the def Output
__doc__ is None
Help on function km_to_miles in module __main__:
km_to_miles(km)
# Convert a distance in kilometres to miles.
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 comment_fallback.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
That is as far as a comment gets: __doc__ is still None, so doctest and the other tools that read __doc__ find
nothing, and the comment helps only while the source file is at hand. A docstring is stored with the function itself.
Key idea
A docstring is kept with the object, where help(), editors and doctest read it; a comment is dropped when Python
reads the file and serves only someone reading the source.
Modules and classes have docstrings too
A module’s docstring is the first statement of its file, and a class’s docstring is the first statement of the class body, written the same way:
"""Convert temperatures between Celsius and Fahrenheit.
Run this file to print a short table. help() on this module shows the
first line next to the module's name.
"""
class Reading:
"""A temperature reading in degrees Celsius."""
def c_to_f(celsius):
"""Return the Fahrenheit value of a temperature in Celsius."""
return celsius * 9 / 5 + 32
print(__doc__.splitlines()[0])
print(Reading.__doc__)
print(c_to_f.__doc__)
for c in (0, 37, 100):
print(f"{c} °C = {c_to_f(c):g} °F") Output
Convert temperatures between Celsius and Fahrenheit. A temperature reading in degrees Celsius. Return the Fahrenheit value of a temperature in Celsius. 0 °C = 32 °F 37 °C = 98.6 °F 100 °C = 212 °F
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 module_docstring.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Inside a file, the module’s docstring is the global name __doc__. PEP 257 says that modules should normally have a
docstring, listing what the module provides, and that a class’s docstring should summarise what the class does.
Classes get their own module later in the track; their docstrings follow the rules of this lesson.
A good first line says what the function returns
PEP 257, Python’s docstring conventions, asks for a few simple things:
- Use triple double quotes,
"""like this""", even for one line, so that the docstring can grow without changes to its quotes. - Make the first line a summary: one phrase that fits on the line and ends with a full stop.
- Write it as a command, saying what the function does: “Return the area of a rectangle.”, “Convert a distance in kilometres to miles.”. PEP 257 asks for “Return …”, not “Returns …”.
- Do not repeat the signature:
help()shows the parameters already. Do say what comes back, which the signature cannot show.
When a function needs more than one line, write the summary, a blank line, and then the details: what each argument means, what is returned, what is raised and any side effect, such as a file that gets written.
def monthly_payment(principal, annual_rate, months):
"""Return the fixed monthly payment that repays a loan.
principal is the amount borrowed, annual_rate the yearly interest
rate in per cent and months the number of monthly payments. With a
rate of 0 the payment is simply principal / months.
Raises ValueError if months is less than 1.
"""
if months < 1:
raise ValueError("months must be 1 or more")
if annual_rate == 0:
return round(principal / months, 2)
r = annual_rate / 100 / 12
return round(principal * r / (1 - (1 + r) ** -months), 2)
print(monthly_payment(500_000, 8.5, 240))
print(monthly_payment(1200, 0, 12))
print(monthly_payment.__doc__.splitlines()[0]) Output
4339.12 100.0 Return the fixed monthly payment that repays a loan.
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 multiline_docstring.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Step through the parts of that docstring and the places that show each one:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- One line, a command. The summary line is one phrase in command form that ends with a full stop and says what the function returns: Return the fixed monthly payment that repays a loan.
- Shown on its own. The last line of the program prints only this line, as documentation tools do in their lists and search results, so it has to make sense on its own.
- A blank line. A blank line ends the summary, as PEP 257 asks. Everything after it is the longer description.
- What the signature cannot say. The details say what a caller cannot see in the signature: what each argument means, that a rate of 0 gives principal / months, and that months below 1 raise ValueError.
- help() shows it all. help(monthly_payment) and an editor's tooltip show the signature and then the whole docstring, without the indentation it has in the source.
The last line of the program prints only the summary, the line that tools show in lists and search results. That is why it has to make sense on its own. PEP 257 does not fix a format for the details. Many projects write them in a style that a documentation tool can read, such as the Google and NumPy styles that the Sphinx documentation generator understands; whichever style a project uses, use it everywhere.
A docstring is indented like the code around it, and Python removes that indentation for you:
def f():
"""Summary line.
Details, indented like the code around them.
"""
print(repr(f.__doc__)) Output
'Summary line.\n\nDetails, indented like the code around them.\n'
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 doc_indent.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Version note
Since Python 3.13 the compiler strips the common indentation of every line of a docstring, so __doc__ holds the
text without it, as above. Python 3.12 and earlier keep the spaces in __doc__, and help() removes them when it
displays the docstring.
Checkpoint
Check: What Python keeps of a docstring · The summary line and the details
doctest runs the examples in a docstring
A docstring may show an example as it would look in the interactive shell, with >>> before what you type and the
expected result on the next line. The standard library’s doctest module finds such examples, runs them and checks
the results:
import doctest
def word_count(text):
"""Return the number of words in text.
>>> word_count("Pack my box with five dozen liquor jugs")
8
>>> word_count(" ")
0
"""
return len(text.split())
# Run the examples in word_count's docstring and report each one.
doctest.run_docstring_examples(word_count, globals(), verbose=True,
name="word_count") Output
Finding tests in word_count
Trying:
word_count("Pack my box with five dozen liquor jugs")
Expecting:
8
ok
Trying:
word_count(" ")
Expecting:
0
ok
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 doctest_demo.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Step through what doctest did with the two examples of word_count():
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- Two examples found. run_docstring_examples() reads the docstring of word_count() and finds two examples, each a line that starts with >>>, with the result it should give on the line below.
- The first example. It runs word_count("Pack my box with five dozen liquor jugs") as if it had been typed after the >>> of the interactive shell.
- 8 and 8. The call gives 8, and the line under the example says 8, so the two match.
- ok. With verbose=True, doctest reports each example as Trying, then Expecting, then ok.
- The second example. It goes back for the second example, word_count(" "), a string of spaces only.
- 0 and 0. split() with no argument finds no words in spaces, so the call gives 0, as the docstring says, and doctest reports ok again.
- When they differ. Had a result differed, doctest would print the example, what it expected and what it got instead, so the documentation cannot quietly go wrong.
run_docstring_examples() runs the examples of one function; with verbose=True it reports each one, and both are
ok. When code and docstring disagree, doctest prints the example, what it expected and what it got instead, so the
documentation cannot quietly go wrong. A whole module is checked with doctest.testmod(), or from the command line with
python -m doctest file.py. The module on testing comes back to doctest, unittest and pytest. Press Edit, change the
8 under the first example to 9 and run the program again: doctest now reports that example as failed, with what it
expected and what it got.
PEP 8 names tell you what kind of thing a name is
import this prints PEP 20, nineteen short principles that sum up Python’s design:
import this Output
The Zen of Python, by Tim Peters Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. Special cases aren't special enough to break the rules. Although practicality beats purity. Errors should never pass silently. Unless explicitly silenced. In the face of ambiguity, refuse the temptation to guess. There should be one-- and preferably only one --obvious way to do it. Although that way may not be obvious at first unless you're Dutch. Now is better than never. Although never is often better than *right* now. If the implementation is hard to explain, it's a bad idea. If the implementation is easy to explain, it may be a good idea. Namespaces are one honking great idea -- let's do more of those!
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 zen.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
“Readability counts” is the reason for everything in this lesson. PEP 8 starts from the same observation: code is read much more often than it is written. Its conventions make Python code look the same from one project to the next, and its naming rules let a reader tell a function from a class or a constant at a glance:
| Criterion | Functions and variables | Classes | Constants |
|---|---|---|---|
| Style | snake_case | CapWords | UPPER_CASE |
| Examples | shipping_cost, weight_kg | Reading, ShoppingCart | BASE_FEE, MAX_RETRIES |
Functions, variables and parameters use lower case with underscores: shipping_cost, weight_kg. Classes use
CapWords: Reading, ShoppingCart. Constants, set once at module level, use capitals with underscores: BASE_FEE,
MAX_RETRIES. A leading underscore, as in _cache, marks a name as internal to its module. The
lesson on variables has the rest: never l, O or I as a one-letter name,
and a trailing underscore, class_, when the name you want is a keyword.
Checkpoint
Check: Examples checked by doctest · PEP 8 names
A consistent layout lets any Python programmer read your file
PEP 8’s layout rules are just as short:
- Indent by four spaces per level. Spaces are preferred to tabs, which PEP 8 keeps only for code that is already indented with them.
- Keep lines to at most 79 characters, and comments and docstrings to 72. PEP 8 lets a team agree on up to 99 characters for code.
- Break a long line inside brackets, and put the break before an operator, as PEP 8 suggests for new code, so that each line starts with the operator that applies to it.
- Leave two blank lines around top-level functions and classes, and one between the methods of a class.
- Put imports at the top of the file, one module per line.
- Put one space on each side of
=,+=and the comparisons, and one after each comma, but none just inside brackets, and none around=in a keyword argument or in the default of a parameter without a type hint:round(cost, 2),express=False. (A parameter with a type hint, a topic of a later module, does get spaces:express: bool = False.) Around arithmetic operators PEP 8 leaves the spacing to your judgment. - Write one statement per line: no
;between statements, and, as PEP 8 prefers, the body of anifon a line of its own.
Here is one function before and after a clean-up. Both files print the same numbers:
Python (before_cleanup.py) · before_cleanup.py
import math,sys
def ShippingCost( weightKg,Distance_km ,express = False ):
#calculate the cost
Base=40;perKg=12.5
cost=Base+perKg*math.ceil(weightKg)+0.8*Distance_km
if express==True: cost=cost*1.5
return round(cost,2)
print(ShippingCost(2.3,120),ShippingCost(0.4,15,express = True)) Output
173.5 96.75
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 before_cleanup.py
Python (after_cleanup.py) · after_cleanup.py
import math
BASE_FEE = 40
FEE_PER_KG = 12.5
FEE_PER_KM = 0.8
EXPRESS_FACTOR = 1.5
def shipping_cost(weight_kg, distance_km, express=False):
"""Return the cost of sending a parcel, rounded to two decimals.
Every started kilogram is charged in full, so 2.3 kg costs as much
as 3 kg.
"""
cost = (BASE_FEE + FEE_PER_KG * math.ceil(weight_kg)
+ FEE_PER_KM * distance_km)
if express:
cost *= EXPRESS_FACTOR
return round(cost, 2)
print(shipping_cost(2.3, 120), shipping_cost(0.4, 15, express=True)) Output
173.5 96.75
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 after_cleanup.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
| Criterion | Before | After |
|---|---|---|
| Names | ShippingCost, weightKg, Distance_km | shipping_cost, weight_kg, distance_km |
| Numbers | 40, 12.5, 0.8 and 1.5 in the code | BASE_FEE, FEE_PER_KG, FEE_PER_KM, EXPRESS_FACTOR |
| Indentation | Two spaces | Four spaces |
| Statements | Two joined with ;, and an if with its body | One per line |
| Imports | import math,sys, with sys unused | import math |
| The test | if express==True: | if express: |
| The calculation | One line without spaces | Wrapped before an operator |
| Explanation | #calculate the cost | A docstring with the rule for kilograms |
Every row is a rule of this lesson, and one more comes from PEP 8’s programming recommendations: it advises against
comparing with True and False, so if express == True: became if express:. The comment that repeated the code
became a docstring that explains a rule: every started kilogram is charged in full.
A comment should say why, not what
A comment is worth its line when it tells the reader something the code cannot: why a value was chosen, why the obvious way is wrong here, what outside rule the code follows. This short program has four comments:
LOAN_DAYS = 21 # books are lent for 14 days
def days_left(days_borrowed):
# subtract days_borrowed from LOAN_DAYS
left = LOAN_DAYS - days_borrowed
# Late books are counted by the fines report, so never show a
# negative number here.
return max(left, 0)
d = 7 # days since the book was borrowed
print(days_left(d), days_left(30)) Output
14 0
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 comments.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
| Criterion | What it does | What to do |
|---|---|---|
| # books are lent for 14 days | Contradicts the code, which says 21 | Fix it, or delete it |
| # subtract days_borrowed from LOAN_DAYS | Repeats the line below it | Delete it |
| # Late books are counted by the fines report … | Explains why max() is there | Keep it |
| # days since the book was borrowed | Explains what the name d means | Rename d to days_borrowed |
The first comment is wrong: someone changed the code and not the comment. PEP 8 puts it plainly: a comment that
contradicts the code is worse than no comment at all. The second repeats the line below it; delete such comments, they
are one more thing to keep up to date. The third explains why the function hides negative numbers. Nobody could guess
that from the code, and someone who removes max() without knowing it would break the report.
The comment on d = 7 explains a name, and a better name, days_borrowed = 7, says the same thing and stays correct
wherever the name is used. Write a comment on a line of its own before the code it explains; an inline comment, after
code on the same line, needs at least two spaces before the # and should be rare. Code that is no longer needed is
deleted, not commented out: version control keeps the old lines.
Key idea
Write a comment to say why; delete one that only says what the line does, fix one that contradicts the code, and replace one that explains a name with a better name.
Checkpoint
Check: PEP 8 layout · Comments that say why
Formatters and linters keep the layout for you
Nobody needs to check spaces and line lengths by hand. A formatter: A tool that rewrites the layout of code into one consistent style, such as Black or Ruff's formatter. It changes spaces and line breaks, never what the code does. rewrites a file’s layout into one consistent style: Black, “the uncompromising code formatter”, and Ruff’s formatter, which is designed as a drop-in replacement for Black, both run with one command. A linter: A tool that reads code and reports problems that a formatter does not fix, such as an unused import or a name that breaks the naming conventions. reports what a formatter does not change, such as names that break the conventions or an unused import; Ruff’s linter can also fix some of those problems when it is asked to. The module on testing and code quality sets both up.
| Criterion | A formatter | A linter |
|---|---|---|
| What it handles | Layout: spaces, line breaks, quotes, indentation | Problems: unused imports, names, likely bugs |
| What it does | Rewrites the file in one style | Reports each problem, and fixes some when asked |
| Examples | Black, ruff format | ruff check |
| When to choose | Mixed tabs and spaces, long lines, inconsistent quotes | An unused import or a name in the wrong style |
Tools cannot write a docstring or decide what a comment should say, and PEP 8 itself warns against following it blindly: consistency within one project matters more than consistency with the guide, and a rule that makes a piece of code harder to read is a rule to break there.
Text Diff Paste the two versions of a function to see every change that a clean-up made.Checkpoint
Check: Formatters and linters
Practise: tidy a module to PEP 8 and PEP 257
marks.py works out percentages and grades, and it works, but it ignores Python’s style conventions. The exercise asks
you to clean it up without changing what any function returns; its sample tests check the names, the docstrings and
the layout, and call the renamed functions:
Exercise · Easy · Python
Tidy a module to PEP 8 and PEP 257 without changing it
marks.py works out percentages and grades, and it works, but it ignores Python's style conventions. Clean it up so that the style checks pass, without changing what any function returns.
Rename to the PEP 8 styles: the constant passMark becomes PASS_MARK, and the functions become percentage(), grade_for() and needs_to_pass(), with parameters and local variables in snake_case (obtained, total, required). The checks then look for:
- a module docstring, and a docstring in every function whose first line is one sentence ending with a full stop;
snake_casenames for every function, parameter and local variable, andUPPER_CASEfor module-level constants;- indentation of four spaces per level, no line longer than 79 characters and one statement per line, so no
;and noifwith its body on the same line; - two blank lines before every function.
The behaviour tests call the renamed functions with the same values as before.
Starter code · marks.py
import math
passMark=40
def Percentage(Obtained,Total):
return round(Obtained*100/Total,1)
def gradeFor(percent):
if percent>=75:return "distinction"
elif percent>=60:return "first class"
elif percent>=passMark:return "pass"
else:return "fail"
def needsToPass(Obtained,Total):
Required=math.ceil(Total*passMark/100);return max(Required-Obtained,0) The sample tests · test_marks.py
import ast
import re
import marks
SNAKE = re.compile(r"^_?[a-z][a-z0-9_]*$")
UPPER = re.compile(r"^_?[A-Z][A-Z0-9_]*$")
def source_lines():
with open(marks.__file__, encoding="utf-8") as source:
return source.read().splitlines()
def tree():
return ast.parse("\n".join(source_lines()))
def functions():
return [n for n in ast.walk(tree()) if isinstance(n, ast.FunctionDef)]
def test_behaviour():
"""the renamed functions return what the old ones did"""
assert marks.PASS_MARK == 40
assert marks.percentage(45, 60) == 75.0
assert marks.percentage(33, 120) == 27.5
assert marks.grade_for(80) == "distinction"
assert marks.grade_for(75) == "distinction"
assert marks.grade_for(74.9) == "first class"
assert marks.grade_for(60) == "first class"
assert marks.grade_for(40) == "pass"
assert marks.grade_for(39.9) == "fail"
assert marks.needs_to_pass(30, 100) == 10
assert marks.needs_to_pass(20, 75) == 10
assert marks.needs_to_pass(50, 100) == 0
def test_module_docstring():
"""the module starts with a docstring"""
assert ast.get_docstring(tree()), "marks.py has no module docstring"
def test_function_docstrings():
"""every function has a docstring whose first line ends with a full stop"""
for node in functions():
doc = ast.get_docstring(node)
assert doc, f"{node.name}() has no docstring"
first = doc.splitlines()[0].strip()
assert first.endswith("."), (
f"the first line of the docstring of {node.name}() "
"does not end with a full stop")
def test_snake_case_names():
"""functions, parameters and local variables use snake_case"""
for node in functions():
assert SNAKE.match(node.name), f"{node.name} is not snake_case"
for arg in node.args.args:
assert SNAKE.match(arg.arg), (
f"the parameter {arg.arg} of {node.name}() is not snake_case")
for inner in ast.walk(node):
stored = isinstance(inner, ast.Name) and isinstance(
inner.ctx, ast.Store)
if stored:
assert SNAKE.match(inner.id), (
f"the variable {inner.id} in {node.name}() "
"is not snake_case")
def test_constants_upper_case():
"""module-level constants use UPPER_CASE"""
for node in tree().body:
constant = isinstance(node, ast.Assign) and isinstance(
node.value, ast.Constant)
if constant:
for target in node.targets:
if isinstance(target, ast.Name):
assert UPPER.match(target.id), (
f"the constant {target.id} is not UPPER_CASE")
def test_layout():
"""four-space indents, short lines and one statement per line"""
for number, line in enumerate(source_lines(), start=1):
assert "\t" not in line, f"line {number} contains a tab"
assert len(line) <= 79, f"line {number} has {len(line)} characters"
seen = set()
for node in ast.walk(tree()):
if isinstance(node, ast.stmt):
# Every statement starts at a multiple of four spaces; a
# continuation line inside brackets may line up as PEP 8 allows.
assert node.col_offset % 4 == 0, (
f"line {node.lineno} is indented by {node.col_offset} spaces")
assert node.lineno not in seen, (
f"line {node.lineno} holds more than one statement")
seen.add(node.lineno)
def test_blank_lines():
"""two blank lines come before every function"""
lines = source_lines()
for node in tree().body:
if isinstance(node, ast.FunctionDef):
before = lines[max(node.lineno - 3, 0):node.lineno - 1]
assert before == ["", ""], (
f"{node.name}() at line {node.lineno} needs two blank lines "
"before it") A hint
Work through the checks one at a time and run the tests after each change. Re-indent each body with four spaces, move every return that follows an if ...: onto its own line, and split a=1;b=2 into two lines. A docstring such as """Return the percentage of total that obtained is, to one decimal.""" goes on the first line of the function's body.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
To finish, a short round of questions on docstrings, doctest, names, layout, comments and tools. They come one at a time, starting where you are least sure, and every answer explains itself:
Practice that adapts to you
Practise this lesson
14 questions about 7 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Fresher · What is the difference between a docstring and a comment in Python?
A docstring is a string literal written as the first statement of a module, class or function. Python stores it in the
object’s __doc__ attribute, so help(), editors, documentation generators and doctest can read it while the program
runs. It describes how to use the object: what it does, its arguments, what it returns and what it raises. A comment
starts with # and is discarded when Python reads the file, so only people reading the source see it. It explains why
the code does what it does, such as a business rule or the reason for an unusual choice. In short, a docstring is for
the users of a function and a comment for whoever maintains it. PEP 257 sets the conventions for docstrings, and PEP 8
says comments must be kept up to date, because one that contradicts the code is worse than none.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
A public function's docstring reads: "This function takes a list and a number and returns stuff. It loops over the list." What would you change so that help(), an editor tooltip and a newcomer all get what they need?
-
Mid to senior
Your team inherits a 40,000-line Python codebase with tabs and spaces mixed, camelCase and snake_case names, and 150-character lines. How would you bring it to PEP 8 without breaking anything or flooding code review?
Key takeaways
- A docstring is the first statement of a module, function or class. Python keeps it as
__doc__, andhelp()and editors show it. - Start with a one-line summary in command form that ends with a full stop (“Return …”). Add a blank line and the details: arguments, return value, errors and side effects.
doctestruns the>>>examples in docstrings and checks their output.- PEP 8:
snake_casefor functions and variables,CapWordsfor classes,UPPER_CASEfor constants; four-space indents, lines of at most 79 characters, two blank lines around top-level definitions. - Comments explain why. Delete comments that repeat the code, fix comments that contradict it, and prefer a better name to a comment that explains a name.
- Let a formatter such as Black or Ruff handle the layout, and a linter report the rest; keep your attention for names, docstrings and comments.
Terms in this lesson
- docstring
- A string written as the first statement of a module, class or function. Python keeps it as the object's __doc__ attribute, which help(), editors and doctest read.
- doctest
- The standard library module that finds examples written after >>> in docstrings, runs them and reports each one whose output differs from the line below it.
- formatter
- A tool that rewrites the layout of code into one consistent style, such as Black or Ruff's formatter. It changes spaces and line breaks, never what the code does.
- linter
- A tool that reads code and reports problems that a formatter does not fix, such as an unused import or a name that breaks the naming conventions.
- PEP 8
- Python's style guide: the naming, layout and comment conventions most Python code follows, such as snake_case names for functions, four-space indents and lines of at most 79 characters.
- snake_case
- Writing a name in lower case with underscores between its words, such as shipping_cost. PEP 8 uses it for functions, variables and parameters.
References
- PEP 257: docstring conventions (Python Software Foundation)
- PEP 8: style guide for Python code (Python Software Foundation)
- PEP 20: the Zen of Python (Python Software Foundation)
- The Python Tutorial: documentation strings (Python Software Foundation)
- The Python Tutorial: intermezzo, coding style (Python Software Foundation)
- Glossary: docstring (Python Software Foundation)
- doctest: test interactive Python examples (Python Software Foundation)
- pydoc: documentation generator and online help system (Python Software Foundation)
- What's New in Python 3.13: other language changes (Python Software Foundation)
- Napoleon: Google and NumPy style docstrings (Sphinx project)
- The Ruff formatter (Astral)
- The Ruff linter (Astral)
- Black: the uncompromising code formatter (The Black project)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress