Loops and testing in Python

Last time we saw an example of a for-loop in Python. In Python we’ll use for-loops in order to write list processing functions; they take the place of the recursive functions with cases expressions we wrote in Pyret. So, let’s try to write a Python equivalent of this Pyret function:

fun my-sum(lst :: List<Number>) -> Number: 
  cases (List) lst: 
    | empty => 0 
    | link(fst, rst) -> fst + my-sum(rst) 
  end 
end

Just as we did in Pyret, we’ll start by writing examples. In Pyret, examples and testing are built in to the language; in Python, we’ll need to include a separate library. We’ve developed a small testing library for Python called `testlight`; you can download it from the course website and put it in your PyCharm projects. We can use testlight to write some tests for our sum function as follows:

def my_sum(lst: list) -> int:
    """returns the sum of a list of numbers"""
    pass

def test_my_sum():
    test("sum empty", my_sum([]), 0)
    test("sum larger list", my_sum([3, 1, 4, 0]), 8)

So, let’s start filling out the body of my_sum. We know we’re going to loop over all of the elements of the list with a for-loop, so we can start with something like this:

def my_sum(lst: list) -> int:
    """returns the sum of a list of numbers"""
    for n in lst:
        pass

A for-loop lets us look at one element of the list at a time. How can we use this in order to get the sum of the whole list? We need to somehow add these individual elements together.

We’ll do this with mutation. Unlike Pyret, Python allows us to change (mutate) the value associated with a name. So, the following code will work in the Python console:

>>> x = 2
>>> x = x + 2
>>> x
4

We can use this to create a running sum that we add to as our for-loop executes:

def my_sum(lst: list) -> int:
    """returns the sum of a list of numbers"""
    running_sum = 0
    for n in lst:
        running_sum = running_sum + n

What should we do at the end of the function? Well, after the for-loop is done executing, we’ve added every element of the list to our running sum. So, we can return that value:

def my_sum(lst: list) -> int:
    """returns the sum of a list of numbers"""
    running_sum = 0
    for n in lst:
        running_sum = running_sum + n
    return running_sum

Let’s compare this function to the equivalent Pyret function:

fun my-sum(lst :: List<Number>) -> Number: 
  cases (List) lst: 
    | empty => 0 
    | link(fst, rst) -> fst + my-sum(rst) 
  end 
end

These functions look quite different, but the two cases of the Pyret function are visible in the Python version: running_sum starts out equal to the value from the empty case (0); the body of the for-loop, on the other hand, looks a lot like the link case.

In lecture, we stepped through this function using PyCharm’s debugger; see the lecture capture for details.

Let’s see another example of a function we can implement with a for-loop. See the lecture capture for details of how we developed this function:

def how_many_courses_in_dept(courses: list, dept: str) -> int:
    """returns the number of courses whose names start with dept"""
    course_count = 0
    for course in courses:
        if course.startswith(dept):
            course_count = course_count + 1
    return course_count

def test_how_many_courses_in_dept():
    test("empty courses", how_many_courses_in_dept([], "CSCI"), 0)
    test("course not in dept", how_many_courses_in_dept(["BIOL1000"], "CSCI"), 0)
    test("courst in dept", how_many_courses_in_dept(["BIOL1000", "CSCI0111"], "CSCI"), 1)