UNB/ CS/ David Bremner/ teaching/ cs2613/ labs/ Lab 18

Background

Counter, generators, and classes

Time
15 minutes
Activity
Demo

Recall our generator based counter from L17.

def make_counter(x):
    print('entering make_counter')
    while True:
        yield x
        print('incrementing x')
        x = x + 1

In Lab 17 we almost simulated the generator behaviour with a closure, except that next(counter) was replaced by counter(). We can provide a compatible interface by using a python class.

class Counter:
    "Simulation of generator using only __next__ and __init__"
    def __init__(self,x):
        self.x = x
        self.first = x

    def __next__(self):




        self.x = self.x + 1
        return self.x - 1

print('first')
counter = Counter(100)
print('second')
print(next(counter))
print('third')
print(next(counter))
print('last')

Fibonacci, again

Time
35 minutes
Activity
individual

Save the generated based Fibonacci example as ~/fcshome/cs2613/L18/fibgen.py

Save the following in ~/fcshome/cs2613/L18/fib.py

#!/usr/bin/python3
class Fib:
    def __init__(self,max):
        self.max = max
        self.a = 0
        self.b = 1

    def __next__(self):
        if self.a < self.max:



        else:
            raise StopIteration

Complete the definition of __next__ so that following test passes. Hint: Consider the following closure based version from Lab 17

from fib import Fib
from fibgen import fibgen

def test_fib_list():

    genfibs=list(fibgen(100))
    fibber=Fib(100)

    fibs=[]
    while True:
        try:
            fibs.append(next(fibber))
        except:
            break

    assert genfibs == fibs

We may wonder why this odd looking while loop is used to build fibs rather than some kind of list comprehension. The following simpler version currently fails:

def test_fib_list_2():
    genfibs=list(fibgen(100))
    classfibs=list(Fib(100))
    assert genfibs==classfibs

This failure is due the fact that we haven't really built an iterator yet. Remember Python works by duck-typing, which means that an iterator is something that provides the right methods.

Add the following method to your Fib class. Observe the test above now passes.

    def __iter__(self):
        return self

In addition to signal to the Python runtime that some object is an iterator, the __iter__ serves to restart the traversal of our "virtual list". If we want iterators to act as lists, then the following should really print the same list of Fibonacci numbers twice

if __name__ == '__main__':
    fibber = Fib(100)
    for n in fibber:
        print(n)
    for n in fibber:
        print(n)

Since it doesn't, let's formalize that as a test. Modify the __init__ and (especially) the __iter__ method so that following test passes.

def test_fib_restart():
    fibobj = Fib(100)
    list1 = list(fibobj)
    list2 = list(fibobj)
    assert list1 == list2

Object copying and equality

Time
25 min
Activity
individual.

Save the following skeleton as ~/fcshome/cs2613/labs/L18/expr.py.

class Expr:
    def __init__(self,op,left,right):
        pass

    def __eq__(self, other):
        """Overrides the default implementation

Replace the definitions of __init__ and __eq__ so that the following test passes. You likely want either the vars builtin function or the __dict__ method.

When complete, the following tests should pass.

from expr import Expr
from copy import deepcopy

six_plus_nine = Expr('+', 6, 9)
six_times_nine = Expr('*', 6, 9)
compound1 =  Expr('+', six_times_nine, six_plus_nine)
compound2 =  Expr('*', six_times_nine, compound1)
compound3 =  Expr('+', compound2, 3)

def test_equality():
    assert six_plus_nine == deepcopy(six_plus_nine)
    assert compound1 == deepcopy(compound1)
    assert compound2 == deepcopy(compound2)
    assert compound3 == deepcopy(compound3)

Arithmetic

Time
25 min
Activity
individual.

Replace the eval method definition in your Expr class so that the following additional test passes; a simple if/elif/else block to test the operator should suffice..

def test_basic():
    assert six_plus_nine.eval() == 15
    assert six_times_nine.eval() == 54

Use deepcopy to update test_basic to make sure that eval does not modify the object self.

If the following test does not already pass, update your eval method so that it does

 def test_recur():
    assert compound1.eval() == 69
    assert compound2.eval() == 3726
    assert compound3.eval() == 3729

Add similar checking for mutation to test_recur