CS 61A Lab 5

Mutable Data and Object-Oriented Programming

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.