Lab 8: Object Oriented Programming

Table of Contents

Deadline

By the end of this lab, you should have submitted the lab08 assignment using the command submit lab08.

This lab is due by 11:59pm on 7/22/2014.

Here is a lab08.py starter file for this lab.

Project 3: Ants Checkoff

Before you leave lab today, make sure to get Question 1 on the Ants project checked off by either a lab assistant or the TA. Once you walk through your answers to the 6 questions with a lab assistant or TA, give your name and cs61a login (i.e. cs61a-tb) to the person who checked you off and they will record it on the whiteboard. While you are waiting to get checked off, feel free to get started on the lab.

Object Oriented Programming

Question 1

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

>>> 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
______
AttributeError: 'Account' object has no attribute 'account_holder'
>>> a.holder
______
'Billy'
>>> class CheckingAccount(Account):
...     def deposit(self, amount):
...         Account.deposit(self, amount)
...         print("Have a nice day!")
...
>>> c = CheckingAccount("Eric")
>>> a.deposit(30)
______
Yes!
>>> c.deposit(30)
______
Yes!
Have a nice day!

Question 2

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. You might have to modify some of the existing methods to make the repeat method work. 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'
class Person(object):

    def __init__(self, name):
        self.name = name
        self.previous = "I squirreled it away before it could catch on fire."

    def say(self, stuff):
        self.previous = 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)

    def repeat(self):
        return self.say(self.previous)

Question 3

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'
>>> steven.repeat()
'the sky is falling the sky is falling'

Consider the following three definitions for DoubleTalker:

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

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

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

DoubleTalker = None # REPLACE ME

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!)

The last one works as intended. For the first and second ones, calling repeat would fail.

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

So you would say

DoubleTalker = DoubleTalker3

Question 4

Here is another design of the Account and CheckingAccount classes:

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!

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
        self.transactions = []

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

    def withdraw(self, amount):
        """Decrease the account balance by amount and return the new balance."""
        self.transactions.append(('withdraw', amount))
        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)

Question 5

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.

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
        self.transactions = []

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

    def withdraw(self, amount):
        """Decrease the account balance by amount and return the
        new balance."""
        self.transactions.append(('withdraw', amount))
        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)

    def deposit_check(self, check):
        if check.payable_to != self.holder or check.deposited:
            print("The police have been notified")
        else:
            self.deposit(check.amount)
            check.deposited = True

class Check(object):
    def __init__(self, payable_to, amount):
        self.payable_to = payable_to
        self.amount = amount
        self.deposited = False

Question 6

We'd like to create a Keyboard class that takes in an arbitrary number of Buttons and stores these Buttons in a dictionary. The keys in the dictionary will be ints that represent the postition on the Keyboard, and the values will be the respective Button. Fill out the methods in the Keyboard class according to each description, using the doctests as a reference for the behavior of a Keyboard.

class Keyboard:
    """A Keyboard takes in an arbitrary amount of buttons, and
    has a dictionary of positions as keys, and values as Buttons.

    >>> b1 = Button(0, "H")
    >>> b2 = Button(1, "I")
    >>> k = Keyboard(b1, b2)
    >>> k.buttons[0].key
    'H'
    >>> k.press(1)
    'I'
    >>> k.typing([0, 1])
    'HI'
    >>> k.typing([1, 0])
    'IH'
    >>> b1.pressed
    2
    >>> b2.pressed
    3
    """

    def __init__(self, *args):
        "*** YOUR CODE HERE ***"

    def press(self, info):
        """Takes in a position of the button pressed, and 
        returns that button's output"""
        "*** YOUR CODE HERE ***"

    def typing(self, typing_input):
        """Takes in a list of positions of buttons pressed, and 
        returns the total output"""
        "*** YOUR CODE HERE ***"

class Button:
    def __init__(self, pos, key):
        self.pos = pos
        self.key = key
        self.pressed = 0
class Keyboard:
    """A Keyboard takes in an arbitrary amount of buttons, and
    has a dictionary of positions as keys, and values as Buttons.

    >>> b1 = Button(0, "H")
    >>> b2 = Button(1, "I")
    >>> k = Keyboard(b1, b2)
    >>> k.buttons[0].key
    'H'
    >>> k.press(1)
    'I'
    >>> k.typing([0, 1])
    'HI'
    >>> k.typing([1, 0])
    'IH'
    >>> b1.pressed
    2
    >>> b2.pressed
    3
    """

    def __init__(self, *args):
        self.buttons = {}
        for button in args:
            self.buttons[button.pos] = button

    def press(self, info):
        """Takes in a position of the button pressed, and 
        returns that button's output"""
        if info in self.buttons.keys():
            b = self.buttons[info]
            b.pressed += 1
            return b.key
        return ''

    def typing(self, typing_input):
        """Takes in a list of positions of buttons pressed, and 
        returns the total output"""
        accumulate = ''
        for pos in typing_input:
            accumulate+=self.press(pos)
        return accumulate

class Button:
    def __init__(self, pos, key):
        self.pos = pos
        self.key = key
        self.pressed = 0

Now that you've implemented a Keyboard class, write a function that takes in a list of positions and a Keyboard and returns what the Keyboard would print given the list of inputs. You should be able to do this in one line of code.

def button_press(positions, keyboard):
    """Takes in a list of positions, and returns what
    keyboard would print

    >>> b1 = Button(8, "B")
    >>> b2 = Button(5, "A")
    >>> b3 = Button(1, "N")
    >>> kboard = Keyboard(b1, b2, b3)
    >>> button_press([8, 5, 1, 5, 1, 5], kboard)
    'BANANA'
    """
    "*** YOUR CODE HERE ***"
def button_press(positions, keyboard):
    """Takes in a list of positions, and returns what
    keyboard would print

    >>> b1 = Button(8, "B")
    >>> b2 = Button(5, "A")
    >>> b3 = Button(1, "N")
    >>> kboard = Keyboard(b1, b2, b3)
    >>> button_press([8, 5, 1, 5, 1, 5], kboard)
    'BANANA'
    """
    return keybord.typing(positions)