Week 11: Debugging

Reading: Think Python Appendix A

Notes

Now it is our turn to debug Python code. Perhaps it will be wise to approach the subject cautiously.

The Basics

Error messages exist to help us. In general, they tell us that some of the code we’ve written cannot be understood by Python. Knowing how to read error messages is important.

Some errors are very easy to read:

  • EOL while scanning string literal tells us that Python was reading a string
    • The string started with quotations of some sort, ' or " or """
    • Python reached the end of the line without finding the ending quotations
  • The carat ^ tells us where in the code the error was found

  • NameError name y is not defined tells us that we have tried to use variable y before assigning anything to it

  • When Python tells us that a variable is not subscriptable, it means we have tried to index something that can’t be indexed
    • x[2] refers to the 3rd element of x - if x is a list, string, or tuple
    • Because x in this example is an int, the “3rd element” can’t be interpreted logically
    • “Subscripting” means using square brackets [ ] to pick out an element of a collection

  • not callable means we tried to call something as if it were a function
    • x is not a function, so we can’t call it
    • This error is common when accidentally using parentheses ( and ) instead of square brackets [ ]

  • Sometimes, we write something that Python is simply “unsure” of how to interpret
  • This results in a general SyntaxError: invalid syntax
  • The carat ^ tells us a lot!
    • It’s pointing at the curly brace {
    • There’s no Python behavior defined for the curly brace here

All of these error examples have been from the interpreter, but when we get error messages from running a Python program written in the editor, we get an extra detail: line numbers

Here’s a program with an error:

errors.py
s = []
for j in range(6):
    s.append(j))
print(s)

  • The error message tells us what’s wrong
  • The line number tells us where the error is
    • The unmatched ) is on line 3

Print Statements Tell The Story

Sometimes we write a program that has no errors, but the program does not do what we intended.

silent_error.py
A = [3, 1, 0, 2, 3, 1]
total = 0
for j in A:
    total += A[j]
print("Final total is", total)
Final total is 9

\(3 + 1 + 0 + 2 + 3 + 1 = 10\)

What happened here?

We could use the visualizer to inspect the code, but we can also add print statements to see what our program is doing:

silent_error.py
A = [3, 1, 0, 2, 3, 1]
total = 0
for j in A:
    print("Total is", total, "|| j is", j, "|| adding", A[j])
    total += A[j]
print("Final total is", total)
Total is 0 || j is 3 || adding 2
Total is 2 || j is 1 || adding 1
Total is 3 || j is 0 || adding 3
Total is 6 || j is 2 || adding 0
Total is 6 || j is 3 || adding 2
Total is 8 || j is 1 || adding 1
Final total is 9

We weren’t adding the quantity in to the total we intended to add!

  • for j in A loops through the values in A directly, not the indices
  • The values all happen to be valid indices
  • We unintentionally were indexing list A based on the values!

Here it is in the visualizer:

The visualizer is a valuable tool, but print statements are often more useful for longer programs.

Tracing Back

  • Error messages in Python code trace the source of the error.
    • When the error occurs in a function, we want to know where it happened in the function
    • We also want to know where the function was called from
error_trace.py
def absolute_value(x):
    if x >= 0:
        return x
    else:
        y == -1 * x
        return y
    
A = [4, 5]
B = [3, 2, -1, 2]
C = []
for k in A:
    C.append(absolute_value(k))
for k in B:
    C.append(absolute_value(k))

  • The code causing the error is on line 5:
    • y == -1 * x is a comparison
    • Assignment is one = : y = -1 * x
    • This code is inside the absolute_value function
  • The absolute_value function is called on line 12 with no error
    • List A contains no negatives
  • The error happens when absolute_value is called on line 14
    • List B contains a negative
    • The code on line 5 is only reached when absolute_value has a negative argument

Practice

Practice Problem 11.1

Practice Problem 11.1

Add print statements to error_trace.py to find the value of C before the error happens, and the value of the argument passed to absolute_value when the error happens.

Practice Problem 11.2

Practice Problem 11.2

Running this program results in an error. Read the error message and figure out what’s wrong, then fix the error.

greater_int.py
def greater_int(x, y):
    if x > y:
        return x
    elif y > x:
        return y
    
Z = [4, 6, 1, 3, 6, 3]
W = []
for i in range(len(Z)):
    first = Z[i]
    second = Z[i+1]
    larger = greater_int(first, second) 
    W.append(larger)
print(W)

Practice Problem 11.3

Practice Problem 11.3

Running this program results in an error. Read the error message and figure out what’s wrong, then fix the error.

greater_int.py
def greater_int(x, y):
    if x > y:
        return x
    elif y > x:
        return y
    
A = [3, 1, 4, 1]
B = [5, 2, 3, 1]
W = []
total = 0
for i in range(len(Z)):
    W[i] = greater_int(A[i], B[i])
    total = total + W[i]
print(total)

Homework

  • Homework problems should always be your individual work. Please review the collaboration policy and ask the course staff if you have questions.

  • Double check your file names and return values. These need to be exact matches for you to get credit.

Homework Problem 11.1

Homework Problem 11.1 (40 pts)

The email_splitter function should split the email into a “name” and a “domain,” returning a tuple

  • The name is what comes before the @ symbol
    • This should be the first element in the returned tuple
  • The domain is what comes after the @ symbol
    • This should be the second element in the returned tuple

The change_email function should call the email_splitter function, and return the original email address, with the domain changed to gwu.edu.

change_email.py
def email_splitter(email):
    split_email = email.split(".")
    name = split_email[0]
    domain = split_email[1]
    return name, domain
    
def change_email(email):
    output = email_splitter(email)
    output += '@gwu.edu'
    return output

Calling change_email('george.washington@gmail.com') should return string 'george.washington@gwu.edu'

It has several bugs. Fix them and submit as change_email.py.

Homework Problem 11.2

Homework Problem 11.2 (40 pts)

The check_lower function should read in words from the file specified by filename and return a list of booleans for whether each word is lowercase or not.

For instance, if the file contains the following:

Make sure you use regular expressions and account for punctuation.

The return value should be:

[False, True, True, True, True, True, True, True, True, True]
lower_check.py
def check_lower(filename)
    with open(filename, 'r') as f:
        contents = f.read()
    lower_words = []
    for word in contents.split():
        if word.lower() == word.lower():
            lower_words.append(True)
        elif word.lower == word:
            lower_words.append(False)
        return lower_words

The program has bugs. Fix them and submit as lower_check.py.

Homework Problem 11.3

Homework Problem 11.3 (20 pts)

The largest_digit function is supposed to take as argument a positive integer, and return the largest digit. The return value should be an integer.

largest_digit.py
def largest_digit(y):
    y_str = str(y):
    largest = 0
    for k in range(y):
        if y_str[k] > largest:
            largest = y_str[k]
    return largest
  • largest_digit(82) should return 8
  • largest_digit(1012) should return 2

The program contains several bugs. Fix them, and submit as largest_digit.py.