Due at 11:59pm on 03/04/2015.
Download lab06.zip. Inside the archive, you will find starter files for the questions in this lab, along with a copy of the OK autograder.
By the end of this lab, you should have submitted the lab with
python3 ok --submit
. You may submit more than once before the
deadline; only the final submission will be graded.
Object-oriented programming (OOP) is a style of programming that
allows you to think of code in terms of "objects." Here's an example of
a Car
class:
class Car(object):
num_wheels = 4
def __init__(self, color):
self.wheels = Car.num_wheels
self.color = color
def drive(self):
if self.wheels <= Car.num_wheels:
return self.color + ' car cannot drive!'
return self.color + ' car goes vroom!'
def pop_tire(self):
if self.wheels > 0:
self.wheels -= 1
Here's some terminology:
Car
class (shown above) describes the behavior and data that
all Car
objects have.instance: a particular occurrence of a class. In Python, we create instances of a class like this:
>>> my_car = Car('red')
my_car
is an instance of the Car
class.
attribute or field: a variable that belongs to the class.
Think of an attribute as a quality of the object: cars have wheels
and color, so we have given our Car
class self.wheels
and
self.color
attributes. We can access attributes using dot
notation:
>>> my_car.color
'red'
>>> my_car.wheels
4
method: Methods are just like normal functions, except that they
are tied to an instance or a class. Think of a method as a "verb" of
the class: cars can drive and also pop their tires, so we have
given our Car
class the methods drive
and pop_tire
. We call
methods using dot notation:
>>> my_car = Car('red')
>>> my_car.drive()
'red car goes vroom!'
constructor: As with data abstraction, constructors describe how
to build an instance of the class. Most classes have a constructor.
In Python, the constructor of the class defined as __init__
. For
example, here is the Car
class's constructor:
def __init__(self, color):
self.wheels = Car.num_wheels
self.color = color
The constructor takes in one argument, color
. As you can see, the
constructor also creates the self.wheels
and self.color
attributes.
self
: in Python, self
is the first parameter for many methods
(in this class, we will only use methods whose first parameter is
self
). When a method is called, self
is bound to an instance of
the class. For example:
>>> my_car = Car('red')
>>> car.drive()
Notice that the drive
method takes in self
as an argument, but it
looks like we didn't pass one in! This is because the dot notation
implicitly passes in car
as self
for us.
When dealing with OOP, there are three types of variables you should be aware of:
color
variable in the __init__
method is a local variable (not
the self.color
variable).instance attribute: Unlike local variables, instance attributes
will still be accessible after method calls have finished. Each
instance of a class keeps its own version of the instance attribute
— for example, we might have two Car
objects, where one's
self.color
is red, and the other's self.color
is blue.
>>> car1 = Car('red')
>>> car2 = Car('blue')
>>> car1.color
'red'
>>> car2.color
'blue'
>>> car1.color = 'yellow'
>>> car1.color
'yellow'
>>> car2.color
'blue'
class attribute: As with instance attributes, class attributes
also persist across method calls. However, unlike instance
attributes, all instances of a class will share the same class
attributes. For example, num_wheels
is a class attribute of the
Car
class.
>>> car1 = Car('red')
>>> car2 = Car('blue')
>>> car1.num_wheels
4
>>> car2.num_wheels
4
>>> Car.num_wheels = 2
>>> car1.num_wheels
2
>>> car2.num_wheels
2
Notice that we can access class attributes by saying <class
name>.<attribute>
, such as Car.num_wheels
, or by saying
<instance>.<attribute>
, such as car1.num_wheels
.
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
______AttributeError: 'Account' object has no attribute 'account_holder'
>>> a.holder
______'Billy'
>>> Account.holder
______AttributeError: type object 'Account' has no attribute 'holder'
>>> Account.interest
______0.02
>>> a.interest
______0.02
>>> Account.interest = 0.03
>>> a.interest
______0.03
>>> a.deposit(1000)
______Yes!
>>> a.balance
______1000
>>> a.interest = 9001
>>> Account.interest
______0.03
Modify the following Person
class to add a repeat
method, which
repeats the last thing said. See the doctests for an example of its
use.
Hint: you will have to modify other methods as well, not just the
repeat
method.
class Person(object):
"""Person class.
>>> 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'
"""
def __init__(self, name):
self.name = name
"*** YOUR CODE HERE ***"
self.previous = "I squirreled it away before it could catch on fire."
def say(self, stuff):
"*** YOUR CODE HERE ***"
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):
"*** YOUR CODE HERE ***"
return self.say(self.previous)
Modify the Account
class from lecture so that it has a new attribute,
transactions
, that is a list keeping track of any transactions
performed. See the doctest for an example.
class Account(object):
"""A bank account that allows deposits and withdrawals.
>>> 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)]
"""
interest = 0.02
def __init__(self, account_holder):
self.balance = 0
self.holder = account_holder
"*** YOUR CODE HERE ***"
self.transactions = []
def deposit(self, amount):
"""Increase the account balance by amount and return the
new balance.
"""
"*** YOUR CODE HERE ***"
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.
"""
"*** YOUR CODE HERE ***"
self.transactions.append(('withdraw', amount)) if amount > self.balance:
return 'Insufficient funds'
self.balance = self.balance - amount
return self.balance
Sometimes we need to run a calculation to determine the value for an instance attribute every time we look it up. We can make a zero argument method behave like an instance attribute by using the property decorator:
class Purse():
"""
A purse class with a bursting property method.
>>> coin_purse = Purse(101)
>>> coin_purse.bursting # Look Mom! No Parentheses!
True
>>> coin_purse.coins -= 10 # Paying taxes
>>> coin_purse.bursting # Not bursting anymore
False
"""
def __init__(self, coins):
self.coins = coins
@property
def bursting(self):
return self.coins > 100
The property method allows us to call a method that has no arguments without adding parentheses.
Implement the class BadBankAccount
, which is a subclass of Account
.
BadBankAccount
allows the account holder to withdraw more money than they
have (i.e. overdraw). Once overdrawn, BadBankAccount
prevents the account
holder from withdrawing money again until the account has a positive balance.
You should also implement the property method overdrawn
, which returns
a boolean that tells whether or not an account is currently locked due to being
overdrawn.
class BadBankAccount(Account):
""" A subclass of bank account that allows an account holder to overdraw
once, and then prevents them from withdrawing more money. You should also
implement the property method overdrawn, which allows an account holder to
check if they are overdrawn.
>>> harold_account = BadBankAccount('Harold')
>>> harold_account.deposit(100) # depositing my paycheck for the week
100
>>> harold_account.withdraw(101) # buying dinner
-1
>>> harold_account.overdrawn
True
>>> harold_account.withdraw(100000)
You have overdrawn, please add more money!
-1
>>> harold_account.deposit(10)
9
>>> harold_account.overdrawn
False
"""
def withdraw(self, amount):
"""Decrease the account balance by amount and return the
new balance.
"""
"*** YOUR CODE HERE ***"
if self.overdrawn:
print('You have overdrawn, please add more money!')
return self.balance self.balance = self.balance - amount
return self.balance
"*** YOUR CODE HERE ***"
@property
def overdrawn(self):
return self.balance < 0
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!")
...
>>> 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!")
...
>>> a = Account("Billy")
>>> a.balance
______0
>>> c = CheckingAccount("Eric")
>>> c.balance
______0
>>> a.deposit(30)
______Yes!
>>> c.deposit(30)
______Yes!
Have a nice day!
>>> c.interest
______0.02
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
that
inherit from the Person
class:
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!)
The last one works as intended. For the first and second ones,
calling repeat
would fail.
The following questions are for extra practice — they can be found in the the lab06_extra.py file. It is recommended that you complete these problems as well, but you do not need to turn them in for credit.
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.
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.
See the doctests for examples of how this code should work.
class CheckingAccount(Account):
"""A bank account that charges for withdrawals.
>>> 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.
"""
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
return self.balance
class Check(object):
"*** YOUR CODE HERE ***"
def __init__(self, payable_to, amount):
self.payable_to = payable_to
self.amount = amount
self.deposited = False
We'd like to create a Keyboard
class that takes in an arbitrary
number of Button
s and stores these Button
s 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 ***"
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"""
"*** YOUR CODE HERE ***"
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"""
"*** YOUR CODE HERE ***"
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