Effective Debugging, with Python and Print Statements
How to Debug, for Beginners
When you first begin to program, you make a lot of mistakes--your code doesn’t always work!
When you're a skilled programmer, you make mistakes and your code doesn’t always work.
Everyone needs debugging skills.
Debugging: What Is It?
Debugging is the process of figuring out what is going wrong with your code.
There are many ways to debug. This tutorial will talk about one specific way: print statement debugging.
Print statement debugging is great for beginners because it doesn't require special tools. It's a great way to develop a sense of how to debug effectively.
How to Debug With Print Statements
To Debug is to look at the values of variables at specific times.
With print statement debugging, you do nothing more than use the print statement.
Let's get acquainted with the print statement, it won't take long:
print "Hello, world!"
That prints a String. You can also print numbers or objects. In general, you can
print the values of variables
I wrote that bold and slanty so you realize all you need to remember that and you know print statement debugging. The rest of this lesson will cover techniques for effective debugging:
- Which code to read
- How to find the source of your bug
Which Variables Should You Look at?
Look at the ones that seem to be misbehaving.
Debugging is not just running your code. It also requires reading your code.
- Read your code, and
- Read your error messages
Which Code Should You Read?
You should have a little bit of an idea of the general area of code where your bug is occurring: Match up the user experience with the code associated with it.
You can narrow your search from there: Place print statements, with line numbers and file names, so you know "where" you are in the code
An example will make clear what I mean. The numbers in the code examples are just line numbers of the file and are not meant to be part of the code.
Sample Code
Here's our misbehaving code:
1 #File1.py
2
3 def foo():
4 return 6
5
6 x = foo()
7 for i in [1, 2, 4, 6, 5]:
8 x += i
9
10 if x > 23:
11 print "Welcome!"
It's very simple and quite contrived. Let's pretend that foo
was actually a highly complex function and we weren't sure what it was returning. Let's also pretend that we didn't expect this code to print Welcome! (It does. Try it out.)
Note: our code already has some print statements in it. print statements are not inherently debugging print statements.
Adding Debugging Print Statements
If Welcome! were printed in other parts of the code base, we might not know, for sure, which code was printing Welcome! erroneously.
We could go to all the parts in our code where Welcome! was printed and add a debugging statement right above the lines of code that print Welcome!
Let's see what that would look like:
10 if x > 23:
11 print "Debugging: File1.py, line 11"
12 print "Welcome!"
Now, instead of just seeing Welcome! printed, we would see the following be printed:
Debugging: File1.py, line 11
Welcome!
With this output, we know for sure that our bug, our unexpected behavior, is occurring in File1.py.
Looking just above line 11, we would see that the print "Welcome!" statement is controlled by the if statement on line 10. Thus, we know the variable x is of interest to us.
Working Backwards to Trace the Error
First thing I would do would be to print the value of x, right after it is set and before it is used:
10 print x
11 if x > 23:
12 print "Debugging: File1.py, line 12"
13 print "Welcome!"
Running our program, we will now see:
24
Debugging: File1.py, line 12
Welcome!
This is good to know. We are no longer surprised to see that if x > 23 is evaluating to True (and so Welcome! is being printed), but we need to know more.
How did x get to be 24? To figure this out, we work backwards.
How Do We Do This?
We can see that the value of x is initially set on line 6 and then is further changed on line 8, which is hit multiple times.
It is clear that lines 7 and 8 will add 18 to whatever foo returns.
Remember, we are pretending that foo is a complex function and we don't know what it returns.
So, we might insert a print statement at line 7 to print the value of x. We would then see that foo is returning 6.
Pretend we are surprised by this. We could continue to work backward--we would find the definition of foo and begin adding print statements within it to see why foo is returning 6.
Summary
-
Use print statements to examine the behavior of your code. This is print statement debugging
-
Find which area of your code is causing the problem by printing line numbers (and file names) in areas that you suspect may be the source of the bug.
-
When you find the correct general area, read the code around that line to get an idea of which variables are important to the behavior of that code. Print the values of those variables.
-
Work backwards to trace the source of the values of those variables.
-
Repeat steps 2 - 5 until you understand the behavior of the bug.
Finally: Always read your error messages.
We Change What We Study
If you're into Quantum Mechanics, you may know the mere act of observing a system changes the system... Software is not quite as bad as a quantum system in this respect, but there's always the risk, when adding new lines of code, that we are changing the behavior of our code.
Always, always, always keep in mind, when you're debugging, to not change the behavior of the system you are examining.
This was very useful. Thanks for writing it up. Kind regards
You’re welcome! Debugging gets a lot more complex and useful once you start to use an IDE. But the general concepts stay the same.