# CS 61A Lab 5

## Starter Files

We've provided a set of starter files with skeleton code for the exercises in the lab. You can get them in the following places:

## Nonlocal

Consider the following function:

``````def make_counter():
"""Makes a counter function.

>>> counter = make_counter()
>>> counter()
1
>>> counter()
2
"""
count = 0
def counter():
count = count + 1
return count
return counter
``````

Try running this function's doctests. You'll find that it causes the following error:

``````UnboundLocalError: local variable 'count' referenced before assignment
``````

Why does this happen? Normally, when we create variables (like ```count = ...``` in `counter`), we create the variable in the local frame. Thus `count` is marked as a local variable in the `counter` function. However, notice that we tried to compute `count + 1` before the local variable was created! That's why we get the `UnboundLocalError`.

To avoid this problem, we introduce the `nonlocal` keyword. It allows us to update a variable in a parent frame. Consider this improved example:

`````` def make_counter():
"""Makes a counter function.

>>> counter = make_counter()
>>> counter()
1
>>> counter()
2
"""
count = 0
def counter():
nonlocal count
count = count + 1
return count
return counter
``````

Notice the `nonlocal count`. This declares the `count` variable as a nonlocal variable, so now we can update `count`.

### Question 1

Predict what Python will display when the following lines are typed into the interpreter:

``````>>> def make_funny_adder(n):
...     def adder(x):
...         if x == 'new':
...             nonlocal n
...             n = n + 1
...         else:
...             return x + n
...     return adder
>>> h = make_funny_adder(3)
>>> h(5)
______
>>> j = make_funny_adder(7)
>>> j(5)
______
>>> h('new')
>>> h(5)
______
``````

### Question 2

Write a function `make_fib` that returns a function that reurns the next Fibonacci number each time it is called.

``````def make_fib():
"""Returns a function that returns the next Fibonacci number
every time it is called.

>>> fib = make_fib()
>>> fib()
0
>>> fib()
1
>>> fib()
1
>>> fib()
2
>>> fib()
3
"""
"*** YOUR CODE HERE ***"
``````

### Question 3

Recall `make_test_dice` from the Hog project. `make_test_dice` takes in a sequence of numbers and returns a zero-argument function. This zero-argument function will cycle through the list, returning one element from the list every time. Implement `make_test_dice`.

``````def make_test_dice(seq):
"""Makes deterministic dice.

>>> dice = make_test_dice([2, 6, 1])
>>> dice()
2
>>> dice()
6
>>> dice()
1
>>> dice()
2
>>> other = make_test_dice([1])
>>> other()
1
>>> dice()
6
"""
"*** YOUR CODE HERE ***"
``````

## Object Oriented Programming

### Question 4

Predict the result of evaluating the following calls in the interpreter. Then try them out yourself!

``````>>> class Account(object):
...     interest = 0.02
...     def __init__(self, account_holder):
...         self.balance = 0
...         self.holder = account_holder
...     def deposit(self, amount):
...         self.balance = self.balance + amount
...         print("Yes!")
...
>>> a = Account("Billy")
>>> a.account_holder
______
``````
``````>>> a.holder
______
``````
``````>>> class CheckingAccount(Account):
...     def __init__(self, account_holder):
...         Account.__init__(self, account_holder)
...     def deposit(self, amount):
...         Account.deposit(self, amount)
...         print("Have a nice day!")
...
>>> c = CheckingAccount("Eric")
>>> a.deposit(30)
______
``````
``````>>> c.deposit(30)
______
``````

### Question 5

Consider the following basic definition of a `Person` class:

``````class Person(object):

def __init__(self, name):
self.name = name

def say(self, stuff):
return stuff

def ask(self, stuff):
return self.say("Would you please " + stuff)

def greet(self):
return self.say("Hello, my name is " + self.name)
``````

Modify this class to add a `repeat` method, which repeats the last thing said. Here's an example of its use:

``````>>> steven = Person("Steven")
>>> steven.repeat()       # starts at whatever value you'd like
"I squirreled it away before it could catch on fire."
>>> steven.say("Hello")
"Hello"
>>> steven.repeat()
"Hello"
>>> steven.greet()
"Hello, my name is Steven"
>>> steven.repeat()
"Hello, my name is Steven"
>>> steven.ask("preserve abstraction barriers")
"Would you please preserve abstraction barriers"
>>> steven.repeat()
"Would you please preserve abstraction barriers"
``````

### Question 6

Suppose now that we wanted to define a class called `DoubleTalker` to represent people who always say things twice:

``````>>> steven = DoubleTalker("Steven")
>>> steven.say("hello")
"hello hello"
>>> steven.say("the sky is falling")
"the sky is falling the sky is falling"
``````

Consider the following three definitions for `DoubleTalker`:

``````class DoubleTalker(Person):
def __init__(self, name):
Person.__init__(self, name)
def say(self, stuff):
return Person.say(self, stuff) + " " + self.repeat()

class DoubleTalker(Person):
def __init__(self, name):
Person.__init__(self, name)
def say(self, stuff):
return stuff + " " + stuff

class DoubleTalker(Person):
def __init__(self, name):
Person.__init__(self, name)
def say(self, stuff):
return Person.say(self, stuff + " " + stuff)
``````

Determine which of these definitions work as intended. Also determine for which of the methods the three versions would respond differently. (Don't forget about the `repeat` method!)

### Question 7

Here are the `Account` and `CheckingAccount` classes from lecture:

``````class Account(object):
"""A bank account that allows deposits and withdrawals."""

interest = 0.02

def __init__(self, account_holder):
self.balance = 0
self.holder = account_holder

def deposit(self, amount):
"""Increase the account balance by amount and return the
new balance."""
self.balance = self.balance + amount
return self.balance

def withdraw(self, amount):
"""Decrease the account balance by amount and return the
new balance."""
if amount > self.balance:
return 'Insufficient funds'
self.balance = self.balance - amount
return self.balance

class CheckingAccount(Account):
"""A bank account that charges for withdrawals."""

withdraw_fee = 1
interest = 0.01

def withdraw(self, amount):
return Account.withdraw(self, amount + self.withdraw_fee)
``````

Modify the code so that both classes have a new attribute, `transactions`, that is a list keeping track of any transactions performed. For example:

``````>>> eric_account = Account(“Eric”)
>>> eric_account.deposit(1000000)   # depositing my paycheck for the week
1000000
>>> eric_account.transactions
[(‘deposit’, 1000000)]
>>> eric_account.withdraw(100)      # buying dinner
999900
>>> eric_account.transactions
[(‘deposit’, 1000000), (‘withdraw’, 100)]
``````

Don't repeat code if you can help it; use inheritance!

### Question 8

We'd like to be able to cash checks, so let's add a `deposit_check` method to our `CheckingAccount` class. It will take a `Check` object as an argument, and check to see if the `payable_to` attribute matches the `CheckingAccount`'s holder. If so, it marks the `Check` as deposited, and adds the amount specified to the `CheckingAccount`'s total. Here's an example:

``````>>> check = Check(“Steven”, 42)  # 42 dollars, payable to Steven
>>> steven_account = CheckingAccount(“Steven”)
>>> eric_account = CheckingAccount(“Eric”)
>>> eric_account.deposit_check(check)  # trying to steal steven’s money
The police have been notified.
>>> eric_account.balance
0
>>> check.deposited
False
>>> steven_account.balance
0
>>> steven_account.deposit_check(check)
42
>>> check.deposited
True
>>> steven_account.deposit_check(check)  # can't cash check twice
The police have been notified.
``````

Write an appropriate `Check` class, and add the `deposit_check` method to the `CheckingAccount` class. Make sure not to copy and paste code! Use inheritance whenever possible.