Code contains errors (also known as bugs — literally the first “bug” was a moth stuck in an electromechanical relay of the Mark II computer).
Table of contents
Exceptions
When Python code fails, it raises an Exception:
>>> 10 * (1/0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>> 4 + spam*3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str
Error handling
Some errors cannot be avoided and we our code to handle them gracefully. This is especially true when handling external inputs.
Python provides the try…except statement to handle exceptions.
In the example, we handle
- incorrect input (detected by a
TypeError
) - division by zero
def normalize(data):
"""Normalize numbers in data to range 0...1"""
try:
xmin = min(data)
xmax = max(data)
except TypeError:
raise TypeError(f"ERROR: data = {data} contains non-numeric input")
datarange = xmax - xmin
try:
normalized_data = [(x - xmin)/datarange for x in data]
except ZeroDivisionError:
print("Range of data is 0, returning 0")
normalized_data = [0.0 for x in data]
return normalized_data
If you want to raise an exception, use
raise ValueError("Incorrect input for data")
Note that one can also simply re-raise an existing exception from an except
block with a bare raise
statement.
Fail early and often — when your code cannot continue safely, it’s better to raise an exception and give up. Calling code can then still decide to handle the exception.
Types of bugs
Bugs can be
- syntax and language errors (easy, relatively speaking)
- input errors (not properly dealing with bad/wrong/unexpected input data): errors that raise exceptions under certain conditions
- logic errors, edge cases, corner cases (hard)
Here we provide an introduction to the absolutely necessary business of identifying and fixing (“squashing”) software bugs. Work through this page and then see More Resources at the end for more advanced material. Bug-hunting can be difficult and tedious but you have to do it — code that produces wrong results is useless.
General strategy
Be clear about what your code ought to do: what are the inputs, what are the outputs? What do you expect to get?
If you get an exception with a stack trace: look at the code that is referenced.
Output intermediate values (e.g.,
print()
calls). Compare what you get to what you expect. Run the program in your head to understand what values you should see.Make one change and re-run. Is the bug fixed? If not, repeat.
Helpful strategies
Have a terminal and an editor open, side by side, so that you can quickly run the modified code. (Don’t forget to save changes before running the code…)
Prototype small code pieces interactively in
ipython
.Modularize code (functions, classes, modules) and test individual pieces of code.
Run programs inside
ipython
using the%run
magic function:%run hello.py
The advantage is that now you can examine variables interactively inside the interpreter.1
Learn to use the Python debugger in ipython (enable with
%pdb
in ipython).
Activity Fix as many bugs as possible!
Code for the examples below comes in a Classroom Activity (set-up link provided in the Canvas LMS). 2
Syntax and language
Bug 1
Print the numbers 0 to 9:
Bug 2
Print the squares of the numbers 0 to 9:
Bug 3
Print “error” for input 0 or the inverse of the input number:
Bug 4
Define the sinc-function \(\mathrm{sinc}(x) = \sin(x)/x\):
Slicing and list building
A common problem is getting the indexing wrong, resulting in IndexError
or subtle errors in the lists themselves. If faced with slicing problems, try to build and slice lists interactively in ipython
and then transfer the working slice recipe to your code. You sometimes may want to create a simpler or shorter list for testing.
Bug 5
Show that my favorite season in Arizona is winter:
Bug 6
Create a list of values -10, -9.8, -9.6, …, -0.2, 0, 0.2, …, 10.
Edge cases
Edge cases occur at the boundaries of the allowed range of input; typically, you have to test them explicitly. (Corner cases occur when multiple edge cases come together.)
Bug 7
The sinc function is defined for all real numbers
but this implementation is incomplete.
- find values for which our function does not produce the correct result
- fix it
- BONUS: plot
sinc(x)
for values from -10 to 10 in steps of 0.2.
Logic errors
Logic errors tend to be the worst because your code runs and might even produce output that looks roughly correct even though it is wrong. You find logic errors by having a good understanding of what your code should produce for a given input and then trying different inputs and following the input through the code.
Bug 8
Create a list of squares of the first 10 natural numbers (0, 1, 2, …, 10) and print their sum 3:
Bug 9
Calculate the position of an object in free fall as a function of time and store time points (in 1-s intervals) and positions in two arrays (for plotting):4
More resources
- Software Carpentry’s lesson on Errors and Exceptions and Debugging
- Scipy lecture notes: Debugging by Gaël Varoquaux (advanced level)
Footnotes
Inside
ipython
you can also run the Python debugger using the%pdb
magic, but this is a more advanced topic. ↩If you do not have the access to the GitHub Classroom (e.g., because you are not taking the PHY432 class) then you can do this exercise by cloning the activity repository yourself. ↩
At least two errors are hidden here. ↩
Just in case: To stop a running Python program, press CONTROL and C at the same time (
CONTROL + c
or written as^C
in Unix documentation). ↩