Ranging from Creation to Debugging

Jed Rembold & Fred Agbo

February 2, 2024

Announcements

  • Feedback on PS1 will be out next week
  • You have Problem Set 2 due on Monday at 10 pm
    • You have everything you need to do all the problems after today
  • Remember to attend your section and also get help from your section leader
  • Use my office hours T, TH, & FR if you want to see me
  • We will be concluding chapter 2&3 of the text today. Hope eveyone is reading the text along
  • Link to Polling https://www.polleverywhere.com/agbofred203

The range() iterable

  • Need an easy way to produce or describe a range of numeric values
  • The built-in range() function handles this and produces the needed iterable object
  • Takes 1, 2, or 3 arguments:
    • Start (default 0): where to start the sequence at
    • Stop (mandatory): the sequence ends just below this value (does not include it!)
    • Step (default 1): what value the sequence counts by

Be careful, the range function will stop one step before the final stop value.

For ranging examples

  • Providing just a stop argument:

    for n in range(5):
        print(n)
  • Providing a start and stop:

    for n in range(1,11):
        print(n)
  • Providing a start, stop, and step:

    for n in range(10,0,-1):
        print(n)

Review Question and Understanding Check

Which of the below blocks of code would print something different from the others?

for n in range(10):
    if n % 2 == 0:
        print(n)
for i in range(0,10,2):
    if i > 0:
        print(i)
for j in '02468':
    L = int(j)
    print(L)
for k in range(0,10):
    if not (k % 2 > 0):
        print(k)

Iterating over sequences

  • We can also use a for loop to iterate directly over a sequence of values

  • We can loop through a string or list to examine each individual character or element

  • Example of looping through a word to count occurrences of a given letter:

    def count_letters(letter, string):
        count = 0
        for character in string:
            if character == letter:
                count += 1
        return count

Test Yourself

  • It is easy to make mistakes when writing code
    • Syntax mistakes
    • Typos or operations mistakes
    • Overall conceptual mistakes
  • Given that making mistakes is entirely natural, it is also imperative that looking for mistakes become entirely natural
  • You known what the desired goal is for basically every problem in this class. Test your code!

Way of Testing

  • Run your code early and often
    • If your code is breaking at something early, it is not going to fix itself if you continue
  • Test outputs directly
    • Problems will generally give several example outputs of desired functions
    • For your own functions, you should know what you want them to be outputting
    • Run a function as isolated as you can make it after writing it, and ensure that it is performing as expected
  • Write code to test code
    • Wherever possible, I provide some automated tests that can be run
    • You can (and should) write your own tests as well. They need not be complicated!!

Assertions

  • You can use Python’s assert statement to write test functions, which take the form:

    assert condition

    where condition is any operation that returns a True or False

  • Assert statements “expect” a condition to yield a True, and if they do, nothing happens

    • No news is good news in this case
  • If an assert condition evaluates to False, an error is raised

  • Naming your test functions starting with the word test_ will make them automatically discoverable by other tools

Testing Example

  • Suppose we wanted to write some checks of the count_letters function from earlier
def test_count_letters():
    """ Runs several tests on the function count_letters """
    assert count_letters("z", "banana") == 0
    assert count_letters("a", "strawberry") == 1
    assert count_letters("A", "apple") == 0
    assert count_letters("e", "eerie") == 3

Algorithms

  • Recall that when approaching a computation problem, you must have an algorithm designed before you start coding
  • An algorithm is a problem-solving strategy, and should be:
    • Clear and unambiguous, in the sense that it is understandable and leaves no gaps
    • Effective, in the sense that each step is actually possible
    • Finite, in the sense that it ends at some point
  • You need to come up with an algorithm before you start coding!

Creating your own Algorithms

  • Some useful hints to keep in mind when constructing your own algorithms:
    • Think about how you would solve the problem without a computer. You can’t write code if you don’t understand what you want the computer to do.
    • Computers are fast! Brute force methods are often very viable, or at least a good starting point.
    • Try to use tools and programming patterns you have already seen. It is often far easier to write programs by assembling pieces from code you have already seen than writing each program entirely from scratch.
      • Common patterns we have already seen include: looping over sequences, and using variables to track/control a loop
    • Recognize that the program you write is highly unlikely to work the first time
      • Errors can occasionally be in your algorithms
      • More often, early on, errors are in your translating of the algorithm into Python (the implementation)

Example: Greatest Factor

  • Suppose we wanted to write a function to compute the greatest factor of a provided number (not including the number itself)

  • Algorithm:

    • Brute force – check all smaller values to see if factor
    • Start at top and work down, means first found is the greatest
    • Check if factor by seeing if remainder 0
def greatest_factor(num):
    """Finds the greatest factor of a number."""
    for i in range(num-1,0,-1):
        if num % i == 0:
            return i

Debugging

If debugging is the process of removing software bugs, then programming must be the process of putting them in.

Edsger W. Dijkstra

  • Everyone makes mistakes when writing code
  • A core skill then is in efficiently finding the bugs that you introduce
  • We’ll spend the rest of today looking at some good practices
    • As always though, practice makes perfect

Strategy #1

Concentrate on what your program IS doing, instead of what it SHOULD be doing.

  • It may be impossible to find code that is missing
  • Instead focus on determining what your program is doing, or why it is behaving a certain way
  • Only once you understand what it is currently doing can you entertain thinking about how to change it productively

Strategy #2

Let Python help you: print or log the state of different variables.

  • Many errors are caused by you expecting a variable to have some content that it doesn’t have
  • Get Python to help you by adding print statements to print those variables out
  • Add print statements in blocks of code that you aren’t sure are being accessed to see if you see a message

Strategy #3

Stop and read. The documentation. The error messages.

Parsing Error Messages

  • Start at the bottom! That is where the general type of error and a short description will show up.
  • Want to know where it happened? Look one line up from that.
    • Will show a copy of the line where the error occurred
    • One line up from that will include the line number
  • Want nicer error messages?
    • The rich library offers some very pretty error messages: install with pip install rich

    • At the top of your code, then include:

      from rich.traceback import install
      install(show_locals=True)

Strategy #4

Use PythonTutor or a debugger to track EXACTLY what is happening in your program.

Strategy #5

Don’t make random changes to your code in the hopes that it will miraculously start working.

  • Making random changes is easy, fast, and doesn’t require a lot of thought
  • Unfortunately it is, at best, a wildly inefficient method of debugging, and at worst, actively detrimental
  • If you don’t know what you need to fix yet, you either haven’t:
    • Defined what you are attempting to do clearly enough, or
    • Understood / tracked your program well enough to know what it is currently doing

Strategy #6

Talk it out.

  • Explaining things verbally, in plain English, uncovers a shocking amount of misconceptions or mistakes
  • Find someone to talk at about your programming issues
    • It isn’t even important that they understand how to code, or even can talk back to you (though that might help in some cases)
    • Rubber Duck Debugging is where a software developer explains an issue out loud to an inanimate rubber duck

Strategy #7

Test your code as you go! Either manually or automatically.

  • Know that everyone makes mistakes. The longer you go without testing that something in your program works, the harder it is to find where the mistake eventually is.
  • Write code that you test in small pieces as you go
    • Decomposition into smaller functions is great for this: test each function individually as you go
    • In the projects we try to construct the Milestones for this exact same purpose
// reveal.js plugins