• No results found

Interlude: Debug your code

In document Real Python Part 1 (Page 56-60)

You’ve probably already discovered how easy it is to make mistakes that IDLE can’t automatically catch for you. As your code becomes longer and more complicated, it can become a lot more difficult to track down the sources of these errors.

When we learned about syntax and run-time errors, I actually left out a third, most difficult type of error that you’ve probably already experienced: the logic error. Logic errors occur when you’ve written a program that, as far as your computer is concerned, is a completely “valid” program that it has no trouble running - but the program doesn’t do what you intended it to do because you made a mistake somewhere.

Programmers use debuggers to help get these bugs out of their programs (we’re clever with names like that), and there’s already a simple debugger built into IDLE that you should learn to use - before you need to use it.

NOTE: Although debugging is the least glamorous and most boring part of program-ming, learning to make good use of a debugger can save you a lot of time in the end. We all make mistakes; it’s a good idea to learn how to find and fix them.

From the file menu of the interactive window of IDLE (not in a script window), click on Debug ->

Debugger to open the Debug Control window.

Notice how the interactive window now shows [DEBUG ON] at the prompt to let you know that the debugger is open. We have a few main options available to us, all of which will be explained shortly:

Go, Step, Over, Out, and Quit.

Keep both the debugger window and the interactive window open, but let’s also start a new script so that we can see how the debugger works:

1 for i in range(1,4):

2

3 j = i*2

4

5 print"i is", i, "and j is", j

If you run this script while you have the debugger open, you’ll notice that it doesn’t get very far.

Actually, it pauses before running anything at all, and the “Stack” window at the top of the debugger says:

1 >>> '__main__'.<module>(), line 1: for i in range(1,4):

All this is telling us is that line 1 (which contains the code “for i in range(1,4):”) is about to be run.

The beginning of the “Stack” line in the debugger refers to the fact that we’re currently in the “main”

section of the script - for instance, as opposed to being in a function definition before the main block of code has been reached.

The debugger allows us to step through the code line by line, keeping track of all our variables as we go. So, let’s go ahead and click once on “Step” in the debugger in order to do that. Watch carefully what happens to the debugger window when you do that…

Now the “Stack” window in the debugger says that we’re about to run line 2 of the code, which means that line 1 has been executed. Below that, the “Globals and Locals” window includes a new variable i that’s been assigned the value 1. This is because the for loop in the first line of our code created this integer i and gave it that starting value. (There are also a few internal system variables listed above it, but we can ignore those.)

Continue hitting the “Step” button to walk through your code line by line, watching what happens in the debugger window. You can track the growing values of i and j as you loop through the for statement, and output is displayed in the interactive window as usual when the print statements are run.

Usually, we only want to debug a particular section of the code, so spending all day clicking the “Step”

button is less than ideal. This is the idea behind setting a breakpoint.

Breakpoints tell the debugger when to start pausing your code. They don’t actually break anything;

they’re more like “hang on a second, let me take a look at things first”-points.

Let’s learn to use breakpoints by working with the following example code, which isn’t quite doing what we want it to do yet:

What we meant for the function addUnderscores() to do was to add underscores around every char-acter in the word passed to it, so that we could give It the input “hello” and it would return the output:

1 _h_e_l_l_o_ _

Instead, all we see right now is:

It might already be obvious to you what our error was, but let’s use the debugger to work through the problem. We know that the problem is occurring somewhere inside in the function - specifically, within the for loop, since we said that new_word should start with a “_” but it clearly doesn’t. So let’s put a breakpoint at the start of the for loop so that we can trace out exactly what’s happening inside.

To set a breakpoint on a line, right-click (Mac: control-click) on that line and select “Set Breakpoint”, which should highlight the line to let you know that the breakpoint is active.

Now we can run the script with the debugger open. It will still pause on the very first line it sees (which is defining the function), but we can select “Go” to run through the code normally. This will save the function in Python’s memory, save the variable phrase as “hello”, call up our function from the print statement… and then pause at our breakpoint, right before entering the for loop.

At this point, we see that we have two local variables defined (they’re “local” because they belong to the function). As expected, we have new_word, which is a string with just the underscore character, and we have word, the variable we passed to the function, with “hello” as its contents.

Click “Step” once and you’ll see that we’ve entered the for loop. The counter we’re using, i, has been given its first value of 0.

Click “Step” one more time, and it might become clearer what’s happening in our code. The variable new_word has taken on the value “h_”… It got rid of our first underscore character already If you click “Step” a few more times, you’ll see that new_word gets set to “e_”, then “l_”, etc. We’ve been overwriting the contents of new_word instead of adding to it, and so we correct the line to:

1 new_word = new_word + word[i] + "_"

Of course, the debugger couldn’t tell us exactly how to fix the problem, but it helped us identify where the problem occurred and what exactly the unexpected behavior was.

You can remove a breakpoint at any time by right-clicking (Mac: control- clicking) and selecting

“Clear Breakpoint”.

In the debugger, “Quit” does just that - immediately quits out of the program entirely, regardless of where you were in the script. The option “Over” is sort of a combination of “Step” and “Go” - it will step over a function or loop. In other words, if you’re about to “Step” into a function with the debugger, you can still run that function’s code without having to “Step” all the way through each line of it - “Over” will take you directly to the end result of running that function. Likewise, if you’re already inside of a function, “Out” will take you directly to the end result of that function.

NOTE: When trying to reopen the debugger, you might see this error:You can only toggle the debugger when idle

It’s most likely because you closed out of the debugger while your script was still running.

Always be sure to hit “Go” or “Quit” when you’re finished with a debugging session in-stead of just closing the debugger, or you might have trouble reopening it. The IDLE debugger isn’t the most carefully crafted pieceof software - sometimes you’ll just have to exit out of IDLE and reopen your script to make this error go away.

Debugging can be tricky and time-consuming, but sometimes it’s the only reliable way to fix errors that you’ve overlooked. However, before turning to the debugger, in some cases you can just use print statements to your advantage to figure out your mistakes much more easily.

The easiest way to do this is to print out the contents of variables at specific points throughout your script. If your code breaks at some point, you can also print out the contents of variables using the interactive window to compare how they appear to how you thought they should look at that point.

For instance, in the previous example we could have added the statement “print new_word” inside of our for loop. Then when we run the script, we’d be able to see how new_word actually grows (or in this case, how it doesn’t grow properly). Just be careful when using print statements this way, especially inside of loops - if you don’t plan out the process well, it’s easy to end up displaying thousands of lines that aren’t informative and only end up slowing down or freezing your program.

Or you could always tryrubber ducking.

Chapter 8

In document Real Python Part 1 (Page 56-60)