If you enter the wrong code, the computer won’t give you the right program. A computer program will always do what you tell it to, but what you tell it to do might not be what you actually want it to do. These errors are bugs in a computer program. Bugs happen when the programmer has not carefully thought about exactly what the program is doing.
There are three types of bugs that can happen in your program:
Syntax errors This type of bug comes from typos. When the Python interpreter sees a syntax error, it’s because your code isn’t written in proper Python language. A Python program with even a single syntax error won’t run.
Runtime errors These are bugs that happen while the program is running. The program will work up until it reaches the line of code with the error, and then the program will terminate with an error message (this is called crashing). The Python interpreter will display a traceback: an error message showing the line containing the problem.
Semantic errors These bugs—which are the trickiest to fix—don’t crash the program, but they prevent the program from doing what the programmer intended it to do. For example, if the programmer wants the variable total to be the sum of the values in variables a, b, and c but writes total = a * b * c, then the value in total will be wrong. This could crash the program later on, but it won’t be immediately obvious where the semantic bug happened.
Finding bugs in a program can be hard, if you even notice them at all! When running your program, you might discover that sometimes functions are not called when they are supposed to be, or maybe they are called too many times. You might code the condition for a while loop incorrectly, so that it loops the wrong number of times. You might write a loop that never exits, a semantic error known as an infinite loop. To stop a program stuck in an infinite loop, you can press CTRL-C in the interactive shell.
In fact, create an infinite loop by entering this code in the interactive shell (remember to press ENTER twice to let the interactive shell know you are done typing in the while block):
>>> while True:
print('Press Ctrl-C to stop this infinite loop!!!')
Now press and hold down CTRL-C to stop the program. The interactive shell will look like this:
Press Ctrl-C to stop this infinite loop!!!
Press Ctrl-C to stop this infinite loop!!!
Press Ctrl-C to stop this infinite loop!!!
Press Ctrl-C to stop this infinite loop!!!
Press Ctrl-C to stop this infinite loop!!!
Traceback (most recent call last):
File "<pyshell#1>", line 2, in <module>
print('Press Ctrl-C to stop this infinite loop!!!')
File "C:\Program Files\Python 3.5\lib\idlelib\PyShell.py", line 1347, in
write
return self.shell.write(s, self.tags)
KeyboardInterrupt
The while loop is always True, so the program will continue printing the same line forever until it is stopped by the user. In this example, we pressed CTRL-C to stop the infinite loop after the while loop had executed five times.
It can be hard to figure out the source of a bug because the lines of code are executed quickly and the values in variables change so often. A debugger is a program that lets you step through your code one line at a time in the same order that Python executes each instruction. The debugger also shows you what values are stored in variables at each step.
In IDLE, open the Dragon Realm game you made in Chapter 5. After opening the dragon.py file, click the interactive shell and then click Debug Debugger to display the Debug Control window (Figure 6-1).
When the debugger is run, the Debug Control window will look like Figure 6-2. Make sure to select the Stack, Locals, Source, and Globals checkboxes.
Now when you run the Dragon Realm game by pressing F5, IDLE’s debugger will activate. This is called running a program under a debugger. When you run a Python program under the debugger, the program will stop before it executes the first instruction. If you click the file editor’s title bar (and you’ve selected the Source checkbox in the Debug Control window), the first instruction is highlighted in gray. The Debug Control window shows the execution is on line 1, which is the import random line.
Figure 6-1: The Debug Control window
Figure 6-2: Running the Dragon Realm game under the debugger
The debugger lets you execute one instruction at a time; this process is called stepping. Execute a single instruction now by clicking the Step button in the Debug Control window. Python will execute the import random instruction, and then stop before it executes the next instruction. The Debug Control window shows you what line is about to be executed when you click the Step button, so the execution should now be on line 2, the import time line. Click the Quit button to terminate the program for now.
Here’s a summary of what happens when you click the Step button as you run Dragon Realm under a debugger. Press F5 to start running Dragon Realm again and then follow these instructions:
Click the Step button twice to run the two import lines.
Click the Step button three more times to execute the three def statements.
Click the Step button again to define the playAgain variable.
Click Go to run the rest of the program or click Quit to terminate the program.
The debugger skipped line 3 because it’s a blank line. Notice you can only step forward with the debugger; you can’t go backward.
The Globals area in the Debug Control window is where all the global variables are displayed. Remember, global variables are variables created outside of any functions (that is, in the global scope).
The text next to the function names in the Globals area looks like "<function checkCave at 0x012859B0>". The module names also have confusing-looking text next to them, like "<module 'random' from 'C:\\Python31\\lib\\random.pyc'>". You don’t need to know what this text means to debug your programs. Just seeing whether the functions and modules are in the Globals area will tell you whether a function has been defined or a module has been imported.
You can also ignore the __builtins__, __doc__, __name__, and other similar lines in the Globals area. (Those are variables that appear in every Python program.)
In the Dragon Realm program, the three def statements, which execute and define functions, will appear in the Globals area of the Debug Control window. When the playAgain variable is created, it will also show up in the Globals area. Next to the variable name will be the string 'yes'. The debugger lets you see the values of all the variables in the program as the program runs. This is useful for fixing bugs.
In addition to the Globals area, there is a Locals area, which shows you the local scope variables and their values. The Locals area will contain variables only when the program execution is inside of a function. When the execution is in the global scope, this area is blank.
If you get tired of clicking the Step button repeatedly and just want the program to run normally, click the Go button at the top of the Debug Control window. This will tell the program to run normally instead of stepping.
To terminate the program entirely, click the Quit button at the top of the Debug Control window. The program will exit immediately. This is helpful if you must start debugging again from the beginning of the program.
Start the Dragon Realm program with the debugger. Keep stepping until the debugger is at line 37. As shown in Figure 6-3, this is the line with displayIntro(). When you click Step again, the debugger will jump into this function call and appear on line 5, the first line in the displayIntro() function. This kind of stepping, which is what you’ve been doing so far, is called stepping into.
When the execution is paused at line 5, you’ll want to stop stepping. If you clicked Step once more, the debugger would step into the print() function. The print() function is one of Python’s built-in functions, so it isn’t useful to step through with the debugger. Python’s own functions—such as print(), input(), str(), and randint()—have been carefully checked for errors. You can assume they’re not the parts causing bugs in your program.
You don’t want to waste time stepping through the internals of the print() function. So, instead of clicking Step to step into the print() function’s code, click Over. This will step over the code inside the print() function. The code inside print() will be executed at normal speed, and then the debugger will pause once the execution returns from print().
Stepping over is a convenient way to skip stepping through code inside a function. The debugger will now be paused at line 38, caveNumber = chooseCave().
Click Step again to step into the chooseCave() function. Keep stepping through the code until line 15, the input() call. The program will wait until you type a response into the interactive shell, just like when you run the program normally. If you try clicking the Step button now, nothing will happen because the program is waiting for a keyboard response.
Figure 6-3: Keep stepping until you reach line 37.
Click back to the interactive shell and type which cave you want to enter. The blinking cursor must be on the bottom line in the interactive shell before you can type. Otherwise, the text you type will not appear.
Once you press ENTER, the debugger will continue to step through lines of code again.
Next, click the Out button on the Debug Control window. This is called stepping out because it will cause the debugger to step over as many lines as it needs to until the execution has returned from the function it is in. After it jumps out, the execution will be on the line after the one that called the function.
If you are not inside a function, clicking Out will cause the debugger to execute all the remaining lines in the program. This is the same behavior that happens when you click the Go button.
Here’s a recap of what each button does:
Go Executes the rest of the code as normal, or until it reaches a breakpoint (see “Setting Breakpoints” on page 73).
Step Executes one instruction or one step. If the line is a function call, the debugger will step into the function.
Over Executes one instruction or one step. If the line is a function call, the debugger won’t step into the function but instead will step over the call.
Out Keeps stepping over lines of code until the debugger leaves the function it was in when Out was clicked. This steps out of the function.
Quit Immediately terminates the program.
Now that we know how to use the debugger, let’s try finding bugs in some programs.
The debugger can help you find the cause of bugs in your program. As an example, here is a small program with a bug. The program comes up with a random addition problem for the user to solve. In the interactive shell, click File New Window to open a new file editor window. Enter this program into that window and save it as buggy.py.
buggy.py
1. import random
2. number1 = random.randint(1, 10)
3. number2 = random.randint(1, 10)
4. print('What is ' + str(number1) + ' + ' + str(number2) + '?')
5. answer = input()
6. if answer == number1 + number2:
7. print('Correct!')
8. else:
9. print('Nope! The answer is ' + str(number1 + number2))
Type the program exactly as it is shown, even if you can already tell what the bug is. Then run the program by pressing F5. Here’s what it might look like when you run the program:
What is 5 + 1?
6
Nope! The answer is 6
That’s a bug! The program doesn’t crash, but it’s not working correctly. The program says the user is wrong even if they enter the correct answer.
Running the program under a debugger will help find the bug’s cause. At the top of the interactive shell, click Debug Debugger to display the Debug Control window. (Make sure you’ve checked the Stack, Source, Locals, and Globals checkboxes.) Then press F5 in the file editor to run the program. This time it will run under the debugger.
The debugger starts at the import random line:
1. import random
Nothing special happens here, so just click Step to execute it. You will see the random module added to the Globals area.
Click Step again to run line 2:
2. number1 = random.randint(1, 10)
A new file editor window will appear with the random.py file. You have stepped inside the randint() function inside the random module. You know Python’s built-in functions won’t be the source of your bugs, so click Out to step out of the randint() function and back to your program. Then close the random.py file’s window. Next time, you can click Over to step over the randint() function instead of stepping into it.
Line 3 is also a randint() function call:
3. number2 = random.randint(1, 10)
Skip stepping into this code by clicking Over.
Line 4 is a print() call to show the player the random numbers:
4. print('What is ' + str(number1) + ' + ' + str(number2) + '?')
You know what numbers the program will print even before it prints them! Just look at the Globals area of the Debug Control window. You can see the number1 and number2 variables, and next to them are the integer values stored in those variables.
The number1 variable has the value 4 and the number2 variable has the value 8. (Your random numbers will probably be different.) When you click Step, the str() function will concatenate the string version of these integers, and the program will display the string in the print() call with these values. When I ran the debugger, it looked like Figure 6-4.
Click Step from line 5 to execute input().
5. answer = input()
The debugger waits until the player enters a response into the program. Enter the correct answer (in my case, 12) in the interactive shell. The debugger will resume and move down to line 6:
6. if answer == number1 + number2:
7. print('Correct!')
Line 6 is an if statement. The condition is that the value in answer must match the sum of number1 and number2. If the condition is True, the debugger will move to line 7. If the condition is False, the debugger will move to line 9. Click Step one more time to find out where it goes.
8. else:
9. print('Nope! The answer is ' + str(number1 + number2))
Figure 6-4: number1 is set to 4, and number2 is set to 8.
The debugger is now on line 9! What happened? The condition in the if statement must have been False. Look at the values for number1, number2, and answer. Notice that number1 and number2 are integers, so their sum would have also been an integer. But answer is a string.
That means that answer == number1 + number2 would have evaluated to '12' == 12. A string value and an integer value will never be equal to each other, so the condition evaluated to False.
That is the bug in the program: the code uses answer when it should have used int(answer). Change line 6 to int(answer) == number1 + number2 and run the program again.
What is 2 + 3?
5
Correct!
Now the program works correctly. Run it one more time and enter a wrong answer on purpose. You’ve now debugged this program! Remember, the computer will run your programs exactly as you type them, even if what you type isn’t what you intend.
Stepping through the code one line at a time might still be too slow. Often you’ll want the program to run at normal speed until it reaches a certain line. You can set a breakpoint on a line when you want the debugger to take control once the execution reaches that line. If you think there’s a problem with your code on, say, line 17, just set a breakpoint on that line (or maybe a few lines before that).
When the execution reaches that line, the program will break into the debugger. Then you can step through lines to see what is happening. Clicking Go will execute the program normally until it reaches another breakpoint or the end of the program.
To set a breakpoint on Windows, right-click the line in the file editor and select Set Breakpoint from the menu that appears. On OS X, CTRL-click to get to a menu and select Set Breakpoint. You can set breakpoints on as many lines as you want. The file editor highlights each breakpoint line in yellow. Figure 6-5 shows an example of what a breakpoint looks like.
Figure 6-5: The file editor with two breakpoints set
To remove the breakpoint, click the line and select Clear Breakpoint from the menu that appears.
Next we’ll look at a program that calls random.randint(0, 1) to simulate coin flips. If the function returns the integer 1, that will be heads, and if it returns the integer 0, that will be tails. The flips variable will track how many coin flips have been done. The heads variable will track how many came up heads.
The program will do coin flips 1,000 times. This would take a person over an hour to do, but the computer can do it in one second! There’s no bug in this program, but the debugger will let us look at the state of the program while it’s running. Enter the following code into the file editor and save it as coinFlips.py. If you get errors after entering this code, compare the code you typed to the book’s code with the online diff tool at https://www.nostarch.com/inventwithpython#diff.
coinFlips.py
1. import random
2. print('I will flip a coin 1000 times. Guess how many times it will come up
heads. (Press enter to begin)')
3. input()
4. flips = 0
5. heads = 0
6. while flips < 1000:
7. if random.randint(0, 1) == 1:
8. heads = heads + 1
9. flips = flips + 1
10.
11. if flips == 900:
12. print('900 flips and there have been ' + str(heads) + ' heads.')
13. if flips == 100:
14. print('At 100 tosses, heads has come up ' + str(heads) + ' times
so far.')
15. if flips == 500:
16. print('Halfway done, and heads has come up ' + str(heads) +
' times.')
17.
18. print()
19. print('Out of 1000 coin tosses, heads came up ' + str(heads) + ' times!')
20. print('Were you close?')
The program runs pretty fast. It spends more time waiting for the user to press ENTER than doing the coin flips. Let’s say you wanted to see it do coin flips one by one. On the interactive shell’s window, click Debug Debugger to bring up the Debug Control window. Then press F5 to run the program.
The program starts in the debugger on line 1. Press Step three times in the Debug Control window to execute the first three lines (that is, lines 1, 2, and 3). You’ll notice the buttons become disabled because input() was called and the interactive shell is waiting for the user to type something. Click the interactive shell and press ENTER. (Be sure to click beneath the text in the interactive shell; otherwise, IDLE might not receive your keystrokes.)
You can click Step a few more times, but you’ll find that it would take quite a while to get through the entire program. Instead, set a breakpoint on lines 12, 14, and 16 so that the debugger breaks in when flips is equal to 900, 100, and 500, respectively. The file editor will highlight these lines as shown in Figure 6-6.
Figure 6-6: Three breakpoints set in coinflips.py
After setting the breakpoints, click Go in the Debug Control window. The program will run at normal speed until it reaches the next breakpoint. When flip is set to 100, the condition for the if statement on line 13 is True. This causes line 14 (where there’s a breakpoint set) to execute, which tells the debugger to stop the program and take over. Look at the Globals area of the Debug Control window to see what the values of flips and heads are.
Click Go again and the program will continue until it reaches the next breakpoint on line 16. Again, see how the values in flips and heads have changed.
Click Go again to continue the execution until the next breakpoint is reached, which is on line 12.
Writing programs is only the first part of programming. The next part is making sure the code you wrote actually works. Debuggers let you step through the code one line at a time. You can examine which lines execute in what order and what values the variables contain. When stepping through line by line is too slow, you can set breakpoints to stop the debugger only at the lines you want.
Using the debugger is a great way to understand what a program is doing. While this book provides explanations of all the game code we use, the debugger can help you find out more on your own.