A document from MCS 275 Spring 2021, instructor David Dumas. You can also get the notebook file.

Worksheet 5

MCS 275 Spring 2021 - David Dumas

Save these puzzles for discussion!

For most worksheets, we encourage doing some work before discussion. This one is a rare exception. It contains a collection of puzzles, and figuring them out ahead of time will make discussion less valuable.

Topics

The main topics of this worksheet are:

  • Debugging Python programs using print(...)
  • Debugging Python programs using pdb.

The main references for these topics are:

Instructions

  • It is important to get some practice with both worksheet topics during discussion. Most problems are presented without any suggestion of which debugging method to use. If you find yourself taking a long time to complete a problem using print(...) debugging, you might consider switching to the next one and using pdb.

  • Do not expect to be prepared for Quiz 5 unless you are able to start programs in pdb, step through them, and inspect values of variables.

1. Broken vending machine

Download the Python program linked below.

It simulates a vending machine, and is similar to a project assigned in MCS 260 in Fall 2020. Run it, use the "help" command in its text interface to learn about the available commands, and try them out.

This script has a bug which affects certain purchases. For example, try starting the script and depositing \$1.15 as four quarters, one dime, and one nickel, and then selecting item 4 (costing \$1.10). The expected behavior would be: The item is purchased, and \$0.05 is given in change. Instead, the script will get stuck (and may require you to press Control-C in the terminal to exit).

Debug the program using an active debugging technique (print(...) or pdb) and find the cause of the infinite loop that prevents the program from executing as intended. Devise and test a fix.

2. Misbehaving function

The function below is supposed to compute the character histogram of a string, with the option to update an existing histogram so that a large text can be processed in chunks. However, it doesn't work: As you'll see form the example code, it doesn't start from a blank histogram in some cases. Why not?

Use debugging techniques to find the first place where the behavior of the function differs from the documented intention of the programmer.

In [5]:
"""Histogram example for debugging"""
# MCS 275 Spring 2021 - David Dumas

def update_char_histogram(s,hist = dict()):
    """Take a string `s` and look at the non-space characters in it. If `hist` is not given,
    compose a histogram of the characters in `s` and return it.  If `hist` is given, assume it contains
    a histogram of another part of the same text, and update it to take into account the text in `s`."""
    for c in s:
        if c.isspace():
            continue
        if c not in hist:
            hist[c] = 0
        hist[c] += 1
    return hist


print("Histogram of 'first line example':")
h = update_char_histogram("first line example") # no hist given, so start from scratch
print(h)
print("Histogram of previous line and 'renewed interest in debugging':")
h = update_char_histogram("renewed interest in debugging",h)
print(h)
print("Histogram of the word 'Mississippi':") # no hist given, so start from scratch
h = update_char_histogram("Mississippi")
print(h) # Unexpected output here; why isn't the default argument of dict() honored?
Histogram of 'first line example':
{'f': 1, 'i': 2, 'r': 1, 's': 1, 't': 1, 'l': 2, 'n': 1, 'e': 3, 'x': 1, 'a': 1, 'm': 1, 'p': 1}
Histogram of previous line and 'renewed interest in debugging':
{'f': 1, 'i': 5, 'r': 3, 's': 2, 't': 3, 'l': 2, 'n': 5, 'e': 9, 'x': 1, 'a': 1, 'm': 1, 'p': 1, 'w': 1, 'd': 2, 'b': 1, 'u': 1, 'g': 3}
Histogram of the word 'Mississippi':
{'f': 1, 'i': 9, 'r': 3, 's': 6, 't': 3, 'l': 2, 'n': 5, 'e': 9, 'x': 1, 'a': 1, 'm': 1, 'p': 3, 'w': 1, 'd': 2, 'b': 1, 'u': 1, 'g': 3, 'M': 1}

3. Tic-tac-toe too easily won

Download the program:

It is a tic-tac-toe game: two players take turns placing 'X' or 'O' on a 3x3 board. The first player to get 3 in a row horizontally, vertically, or diagonally wins.

But this game has a strange bug: No matter where the first player puts their 'X', they win.

Use debugging techniques to find the first place where the behavior of the program differs from the programmer's intention, and to fix it.

Note: This example was based on a collection of unintuitive Python language behaviors collected by Satwik Kansal. The URL for that source document is included below, but be warned, it contains spoilers for this problem (and some crude language). Opening the page is not recommended until after discussion is over:

  • https://github.com/satwikkansal/wtfpython#-a-tic-tac-toe-where-x-wins-in-the-first-attempt

4. A semi-chaotic sequence

Download the program:

This program prints the first 1,000,000 terms of a sequence of integers. It does not have docstrings, which may make exploring the code in a debugger more of a challenge.

In this program, the list of integers from the sequence is stored in the computed_terms list.

Use the debugger (pdb) to answer the following questions about this program:

  1. When computed_terms first has length four, the last element of that list is computed as a sum of previous terms from the list. Which ones?
  2. The first time the function reducer is called, what is the value of t in the main program?
  3. What is the first four-digit number printed by the program?
    1. This will take so many iterations that you won't want to do it by hand. Instead, I suggest adding a check for a four-digit answer to the program that raises an Exception when one is found. Then use pdb to do post mortem analysis on that exception.
    2. Which of the two return statements in function reducer is executed when calculating that term in the sequence?

5. Unexpected exception competition

Working with the other members of your breakout room, write a Python program that looks like it will work but which actually exits with an exception. Your goal to should to make something that will pass a casual inspection by your peers, creating the impression that the program would do a certain thing, while actually containing a hidden bug. The program's docstring should describe its desired (not actual) behavior. Try to conceal the circumstances leading to the exception, so that even the traceback shown on exit doesn't immediately reveal the true source of the problem. Follow these rules:

  • Do not use any web searches!
  • The program must be completely contained in one .py file, and not depend on any modules other than the Python standard library.
  • The program should not modify any files on the computer where it is run.

When you are done, use the Zoom "Ask for help" function, and give your code to the TA. She will swap your buggy programs, and you will try to solve another team's problem.

Your task is to run the program in pdb and to use post-mortem analysis to determine the complete cause of the exception, including what the software author may have intended. Then we will rejoin the Main Room, and each group will explain the other team's bug. Don't just say "the list was empty, and element 0 was requested, producing IndexError"; you'll want to explain exactly how the program's operation led to that circumstance.