Contents
Testing
- All functions require examples (test cases), including helper functions.
- Examples should generally be written before you start writing your function. Writing good examples will help you understand how your function should behave on different inputs.
- Examples cases should reflect various input scenarios so that you exercise the possible behaviors of your function. You do not need to test every input or every input combination – which may be unbounded! – but you should have enough to be confident that your function works with various inputs.
- In particular, your examples should cover edge cases
– inputs at the boundaries of particular ranges. For instance,
many functions that have a number as an input should include an example
where that input is
0
. - If a function has a side effect (like modifying a list), test that side effect.
- If a function both has a side effect and returns information, test both the main return and the side effect.
- Even if your function does not work as expected, you can still write examples demonstrating what the function should do and receive full testing credit.
Example
As an example, let’s consider a function compare
that takes two numerical inputs, x
and y
, and
returns a string. It should return:
"way less"
ifx
is smaller thany - 2
,"about the same"
ifx
is betweeny - 2
andy + 2
(inclusive), and"way more"
ifx
is greater thany + 2
.
Here’s a good set of examples for this function:
def compare(x: int, y: int) -> str: """Describe the relationship between input numbers x and y""" ... # Function body elided assert compare(1, 40) == "way less" assert compare(0, 3) == "way less" assert compare(8, 10) == "about the same" assert compare(1, 1) == "about the same" assert compare(0, 1) == "about the same" assert compare(17, 20) == "way more"
Notice that these examples cover each interesting case of the
program. They also include edge cases testing the boundaries. For
instance, compare(8, 10)
is an edge case, since it’s at the
boundary between the "way less"
and "about the same"
cases.
When writing examples before implementing a function, it may be helpful to write the expected results as expressions rather than values, as a way of figuring out what the function body could look like:
def times_four(x: int) -> int: """Multiplies input x by four""" ... # Function body elided assert times_four(3) == 3 * 4 assert times_four(0) == 0 * 4 assert times_four(-1) == -1 * 4
Design
Use constants where appropriate.
Use helper functions where appropriate.
Make sure your helper functions and constants are not redundant in light of existing built-in functions.
For instance, there is no point in making this function:
def string_to_lower(s: str) -> str: return s.lower()
since you could always use
s.lower()
instead.- Functions should be concise; aim to keep them under 30 lines of code.
Docstrings
Write docstrings for all functions, including helper and nested functions.
A good docstring gives a description of the function, including its input(s) and output. Ideally, by looking at the docstring you know what the function does and how to use it without looking at the function body itself. The standard practices for writing docstrings are described in PEP 257.
You can include docstrings as a comment with
"""
just below your function signatures. E.g.,def sum_of_list(l: list[int]) -> int: """Compute the sum of integers in a list""" ...
Bad docstring descriptions:
"""helper function for table processing"""
"""output the right string for this problem"""
"""uses "where" to filter the table..."""
– how your function body actually works is irrelevant to the docstring!
Names and signatures
- Names should be short but descriptive.
- Most names should be lowercase with words separated by underscores,
e.g.,
age
orfirst_name
. - Constants, whose value never changes once initialized, should be
all-uppercase, e.g.,
PI = 3.14159
. - All functions require type annotations on inputs and output. You can
omit the output annotation for functions that have no return (for example,
functions that only need to
print
).
Formatting
For general guidelines on Python style, see PEP 8.Keep lines under 80 characters.
Colab will show a vertical line when a line is too long. Ensure it does so at (or before) 80 characters by going to Settings → Editor → Vertical ruler column.Indent your code properly. This should with four spaces (not tab characters) per level of indentation.
Again Colab can help you out: Settings → Editor → Indentation width in spaces should be set to
4
, and I recommend checking Show indentation guides as well.return
statements should be written like this:return value
not like
return(value)
if
statements should be written with newlines:if condition1: print("condition1") elif condition2: print("condition2") else: print("condition3")
- In Python, single quotes and double quotes can both be used for strings. You can use either one, but try to be consistent unless there’s a reason to switch.
Put one space between operators and variables, e.g.,
x = y + 5
And not
x=y + 5 x = y+5 x=y+5
Likewise, put one space between arguments to a function:
print(18, 19, "hello")
-
Separate groups of connected statements with a blank line:
list1 = [1, 2, 3, 4] list2 = [4, 5, 6, 7] list1 = list2 list1.append(18) list2.append(17) print(list1) print(list2)
Don’t crowd too many lines together, which makes your code hard to follow.