Debugging support tools have been around since the 70s. All modern IDEs let you control the steps of program execution while showing the program state. Debugging tools, properly used, are much more efficient than print
statements.
If you didn’t do it in the Debugging Basics lab, create a debugging-lab/
directory and download bad_math.py to it.
debugging-lab/
directory and open bad_math.py
in an editor.F5
key.Debug 'bad_math'
.The PyCharm debugger should now launch. Notice that you are now in the Debugging pane of PyCharm, which is accessible anytime from the left sidebar. This pane will open any time you Run a program with debugging.
You should see something similar to the following:
The bad_math.py
program should crash with an exception. Here are the essential elements you see:
Using the step controls, hit either the green “play” icon or the red “stop” icon. Stop will cancel execution and produce nothing, play will continue execution of the program, resulting in the exception printing in the Terminal (where the program is running) and the program will crash.
The PyCharm debugger will automatically break (pause) execution on steps that throw an exception. You can look at the variable pane and call stack to understand the state of the program and hopefully gain insight into what happened.
However, you will often want to break execution at step of your choosing, not just when an exception happens. Maybe want to see how a value was computed and what the variables were well before the crash happened. Or maybe your program doesn’t crash at all, but simply produces the wrong output.
You add breakpoints in the IDE to tell the debugger on which step(s) to pause execution. To set a breakpoint:
line 3
.F5
or right-clicking and Debug 'bad_math'
.line 3
or on whichever line you placed the breakpoint.largest
and numbers
in both the editor and the variable pane.if my_fun(x) == True
, the debugger will step into the my_fun()
function and step through it. If you did step over, the debugger would evaluate the entire line including the my_fun()
call without pausing.random.randint(0,10)
which is a function imported from a Python library. Step Into will take you to the implementation of randint()
. Step Into My Code will skip it because you did not write that code.Use the controls to Step Over a few lines. Notice that the variable pane, watch pane, and call stack update with each step.
Using breakpoints and the step controls, you can precisely control the execution of the program to more methodically track down what is going on.
The Threads & Variables pane shows all variables in scope at each step. In bigger programs, the variable list can be huge and you won’t care about most of them. To help, you can specify watch variables and watch expressions that always display at the top of the Threads & Variables
pane.
To set a watch variable:
threads & variables
Add to Watches
Now you will see your watched variables update as you step through the program. You can add as many watch variables as you like.
You can also watch a complete expression, such as a boolean comparison. This can be particularly useful for debugging if-else
statements and loops.
To set a watch expression:
if numbers[i] > largest:
, select only the numbers[i] > largest
portion of the statement.Add to Watches
.You will also find it useful to only have a breakpoint trigger under certain conditions.
For example, you are reading file of 10,000 hospital patient records and you figure out that the program crashes when it gets to the record belonging to “Alice St. John”. Unfortunately, Alice is record 342. You don’t want to set a breakpoint on the offending line and have to hit the Continue control 341 times to figure out what’s going on with Alice’s data.
Enter the conditional breakpoint, which is a breakpoint that only pauses execution when an expression you specify evaluates to True
. Try it with our bad_math.py
sample:
largest == 12
in the textbox.largest == 12
.Conditional breakpoints are extremely useful for refining your hypothesis as to what’s going on. Note you can enter any Python expression that evaluates to True
or False
, for example:
largest == 12 and i < 8
largest >= 5
When running your program, you have the option to Debug
or Run
. What’s the difference?
Run will not pause on breakpoints or exception, nor will variable values be tracked. Your breakpoints and watch variables will remain in PyCharm, but they are not updated.
Debug will do everything we showed, but significantly slows down the execution time of your program. This is because to enable debugging your code must be instrumented to enable the debugger to control execution and evalute variable or expression values. Think of instrumentation as adding an if breakpoint is True
before every line of code as well as a print statement. Running debug mode for a large, complex system, can be costly.
There are 4 bugs present in the initial bad_math.py
that can be triggered based on which value the numbers
variable has. The various calls to main()
at the bottom of the file are sufficient to reveal all the bugs.
Find and remove them. There are multiple ways to squash the bugs. You may squash two bugs at once depending on how you fix the first bug that causes the exception we have seen in our examples.
Your output should look like the following if you gracefully fix the bugs:
Numbers: [2, 8, 1, 6, 3, 12, 5, 9]
The largest number is: 12
The average is: 5.75
✅ All calculations are correct.
--------
Numbers: [32, 16, 8, 4, 2, 1, 0]
The largest number is: 32
The average is: 9.0
✅ All calculations are correct.
--------
Numbers: []
The list of numbers cannot be empty.
--------
Numbers: [2]
The largest number is: 2
The average is: 2.0
✅ All calculations are correct.
--------
Numbers: [12, 12]
The largest number is: 12
The average is: 12.0
✅ All calculations are correct.
--------
x
evaluates to False
?Step Over
and Step Into
in terms of the next step of execution?Move on to the More Practice lab to continue working with the debugger.