3.7. Good Coding Practices: Debugging¶
As we are getting into more complex code constructions, there are more chances for the code to contain errors. Now is a good time to develop skills for dealing with those errors.
A simple fact of programming is that code almost never works the first time. Debugging is the process of finding and fixing bugs (errors) in code, and it is a critical part of programming.
To debug your code, you have to first understand what has gone wrong, then try to figure out why.
Three Steps for Debugging
Step 1: What should it do?
Make sure you know exactly what the code should do. If you donât have a clear idea of what program should do, it will be difficult to figure out how itâs going wrong.
Step 2: What does it do?
Run the program, and pay close attention to everything it does. For now, that will mostly involve observing its output.
Step 3: Where do those two diverge?
The point at which the program starts to do something different than what you expect is the point at which you can focus your debugging efforts. Once you know what it should do, what it does do, and how those differ, you can start thinking about why that difference might occur. What could have caused that? Where in the code would it happen? Focus there.
There are three main types of errors you might encounter. Understanding each type will help you analyze and fix them when you do.
3.7.1. Syntax Errors¶
Syntax errors are errors in the form of the code itself, when it doesnât conform to the syntax rules of the programming language.
For example, if you put a space in a variable name, Python thinks it is two operands without an operator, which is invalid:
>>> bad name = 5
SyntaxError: invalid syntax
Or code might violate rules about how numbers can be formatted:
>>> month = 09
File "<stdin>", line 1
month = 09
^
SyntaxError: invalid token
The most common messages are SyntaxError: invalid syntax
and SyntaxError:
invalid token
, neither of which is very informative by itself. But these are
the most straightforward to debug. The answer to âwhat should it do?â is ânot
crash,â and Python will tell you exactly where in the program it diverges from
that expectation (by crashing).
To debug these, look closely at where Python is saying the error occurred, and think through all of the syntax rules you know that are relevant to that line and its surroundings.
At this point, the syntax errors you are most likely to make are either simple
typing mistakes or using illegal variable names, like class
and yield
,
which are keywords, or odd~job
and US$
, which contain illegal
characters.
3.7.2. Runtime Errors¶
Runtime errors occur when something goes wrong as the program is running. These are not caused by invalid syntax; the syntax is correct, but it tells Python to do something that is not possible or not allowed.
The runtime error you are most likely to make is a NameError
, caused by
trying to use a non-existent variable in an expression. This can happen if you
try to use a variable before you have assigned it a value or if you spell a
variable name wrong:
>>> principal = 327.68
>>> interest = principle * rate
NameError: name 'principle' is not defined
Also remember: variables names are case sensitive, so LaTeX
is not the same
as latex
.
As with syntax errors, runtime errors will tell you where in the code they occurred. However, that line is not necessarily the location of the bug itself. The code might have done something wrong earlier that only resulted in an invalid operation later on, and Python wonât know that. You may have to spend more time comparing details of what the program should do and what it does do to debug runtime errors.
3.7.3. Semantic Errors¶
Weâve discussed and seen several examples already of semantic errors. These occur when the program has valid syntax and runs without crashing, but it does not do what you, the programmer, were wanting or expecting it to do.
Weâve seen the following semantic errors so far:
Writing an integer with commas, like
1,000,000
. Python interprets that as a sequence of multiple values, not just one integer.Trying to use an invalid variable name like
pop-tarts
. That is valid Python syntax, but it is an expression subtracting the value oftarts
from the value ofpop
.Using
input()
to ask the user to enter a number without usingint()
orfloat()
to convert it to a numeric type. See ActiveCode input02 for an example, and input03 for a corrected version.
At this point, the next most likely cause of a semantic error is the order of operations. For example, to evaluate \(\frac{1}{2\pi}\), you might be tempted to write
1.0 / 2.0 * pi
But the division happens first, so you would get \(\frac{\pi}{2}\), which is not the same thing! There is no way for Python to know what you meant to write, so in this case you donât get an error message; you just get the wrong answer.
Semantic errors donât give you help in the form of a crash report pointing to a particular line. For these, you always have to spend time comparing what the program should do to what it does do. But what if the program doesnât output much, if anything at all, on the way to doing something wrong?
3.7.4. Print Debugging¶
One simple tool that can help you understand more about what your code is doing as it runs is print debugging. This simply means adding print statements to the code that will show you the values of a programâs variables as it is running. These print statements are not needed for the final program; theyâre just temporary, added for the purpose of giving you more visibility into the programâs internal state.
As an example, imagine youâre using a for
loop and the range()
function
to perform a simple calculation:
The code outputs a result of 120. But the product should be 720. So clearly the program isnât working as intended, but what is going wrong? All we see is the incorrect output. We need to know more about what is happening before that.
The following program has added a single print statement, printing the value of
i
inside the loop. Run it and look at the output. Can you see what is
going wrong? You might even think of a fix, even though we havenât learned how
much of this code works yet.
Here, we expect the values of i
to go from 1 to 6, or at least thatâs
what we intended for the code to do. But by printing out the values i
is
actually getting, we see it only goes up to 5. That might be enough to lead us
to remember that the sequence of numbers produced by range()
goes up to
but does not include the value we specify for its end point. We can then see
that the call to range()
should be changed to have an argument of 7 in
place of 6, at which point the code will work correctly.
This is a very useful technique. In general, you can think about what a program should do at each step, make it show you what it is doing at each step, and look for the point at which it does something different than what you want or expect. Print statements are an easy way to âsee insideâ the program as it runs. The CodeLens tool in this book and the Python Tutor tool on which it is based provide another way. Professional programmers often use programs called debuggers that provide powerful tools for doing this kind of investigation in more complex code.