Function Debugging

To write code is to see a lot of bugs. When your code is not doing what you want, here are three strategies you can try.

1. Read the Exception Message

Some bugs cause the code to fail with an error as it runs a particular line - called an "exception" on that line. Python exceptions produce these fairly ugly looking printouts as shown below. The key is: read the exception from the bottom up.

      ....
        exec(compile(example.source, filename, "single",
      File "", line 1, in 
        digits_only('Hi4x3')
      File "str1.py", line 34, in digits_only
        result += s[i + 1]
    IndexError: string index out of range

1. The bottom most line describes the specific error the code ran into. Read that error message, as often it is an extremely good clue. In this case, the error message is "IndexError: string index out of range"

2. Above the error message is a listing of which line of code ran into the error. You may need to look up a few lines to find the code and line that you are working on. In this case, it directs us to: str1.py, line 34

Many bugs can be solved with those two pieces of information: what the error was, and what line caused it. With the error in mind, go look at that line of code. Is there something wrong with the logic of this code that could cause this error?

In this case, the line refers to s[i + 1] which is incorrect code, causing it to refer past the end of the string, resulting in the IndexError. The correct code is s[i].

2. Look at the Got

It's tempting to look at your code and think "why does this not work?" That question is not so helpful. A better question is "why does the code produce this output?". Look at the actual output (the "got" value). Look at the code and the output it produced. Mentally trace through the code, try see how its logic and loops produce that output.

3. Add a Couple print() Calls

Sometimes just knowing the line number to look at is enough for you to spot the problem. But for a more complicated case, you may need more data to understand what the code is doing.

A relatively easy way to see what the code is doing is adding a few print() function calls in the code to make the values used by the code visible as it runs.

For example, here is the digits_only() code with a two print() calls added to show the values used in the loop (see stri1.zip for this code).

def digits_only(s):
    """Return the digit chars of a string."""
    result = ''
    print('s:', s)
    for i in range(len(s)):
        print('i:', i, 's[i]:', s[i])
        if s[i].isdigit():
            result += s[i]
    return result

Running digits_only('Hi4x3') with a Doctest, we see the output from the print() calls, followed by the function's return value:

    digits_only('Hi4x3')
Expected:
    '43'
Got:
    s: Hi4x3
    i: 0 s[i]: H
    i: 1 s[i]: i
    i: 2 s[i]: 4
    i: 3 s[i]: x
    i: 4 s[i]: 3
    '43'

The printing causes the Doctest to fail, since the Doctest system interprets these lines of text as the output of the function, and it was expecting to get just '43'. Use the printout as a temporary measure to think about the run of the code. When the print() calls are removed, the logic of the Doctest will work again.

print() vs. In Your Head

Some bugs are simple enough that you can look at the code, and knowing what the error is, see how the code is going wrong. You are basically modeling the run of the code in your head, and many bugs are simple enough for that to work.

But sometimes the run of the code is too complicated to think it through in your head. Adding some print() calls in the code lets the computer do the work of showing you the series of values used in the code.

This is another advantage of having Doctests set up - you can add a couple print() calls, and then use the Doctest right-click to run the code and see the printout right away. (See the Doctest section for more information.)

When the code is fixed, take the print() calls out, or at least comment them out. They should not be in the production version of the code.

 

Copyright 2020 Nick Parlante