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

# 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')
```
• Observe that the implimentation is closer to the closure based version of L17 than the original generator version.
• It's a recurring theme in Python to have some syntax/builtin-function tied to defining a special `__foo__` method

# 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`