Lab 6: Object-Oriented Programming lab06.zip

What Would Python Do?

These questions use inheritance. For an overview of inheritance, see the inheritance portion of Composing Programs.

Q1: WWPD: Classy Cars

Below is the definition of a Car class that we will be using in the following WWPD questions.

Note: The Car class definition can also be found in car.py.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Car:
num_wheels = 4
gas = 30
headlights = 2
size = 'Tiny'

def __init__(self, make, model):
self.make = make
self.model = model
self.color = 'No color yet. You need to paint me.'
self.wheels = Car.num_wheels
self.gas = Car.gas

def paint(self, color):
self.color = color
return self.make + ' ' + self.model + ' is now ' + color

def drive(self):
if self.wheels < Car.num_wheels or self.gas <= 0:
return 'Cannot drive!'
self.gas -= 10
return self.make + ' ' + self.model + ' goes vroom!'

def pop_tire(self):
if self.wheels > 0:
self.wheels -= 1

def fill_gas(self):
self.gas += 20
return 'Gas level: ' + str(self.gas)

For the later unlocking questions, we will be referencing the MonsterTruck class below.

Note: The MonsterTruck class definition can also be found in car.py.

1
2
3
4
5
6
7
8
9
class MonsterTruck(Car):
size = 'Monster'

def rev(self):
print('Vroom! This Monster Truck is huge!')

def drive(self):
self.rev()
return super().drive()

You can find the unlocking questions below.

Use Ok to test your knowledge with the following “What Would Python Display?” questions:

1
python3 ok -q wwpd-car -u✂️

Important: For all WWPD questions, type Function if you believe the answer is <function...>, Error if it errors, and Nothing if nothing is displayed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
>>> deneros_car = Car('Tesla', 'Model S')
>>> deneros_car.model

______
>>> deneros_car.gas = 10
>>> deneros_car.drive()

______
>>> deneros_car.drive()

______
>>> deneros_car.fill_gas()

______
>>> deneros_car.gas

______
>>> Car.gas

______
>>> deneros_car = Car('Tesla', 'Model S')
>>> deneros_car.wheels = 2
>>> deneros_car.wheels

______
>>> Car.num_wheels

______
>>> deneros_car.drive()

______
>>> Car.drive()

______
>>> Car.drive(deneros_car)

______
>>> deneros_car = MonsterTruck('Monster', 'Batmobile')
>>> deneros_car.drive()

______
>>> Car.drive(deneros_car)

______
>>> MonsterTruck.drive(deneros_car)

______
>>> Car.rev(deneros_car)

______
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108

What would Python display? If you get stuck, try it out in the Python
interpreter!

>>> from car import *
>>> deneros_car = Car('Tesla', 'Model S')
>>> deneros_car.model
? Model S
-- Not quite. Try again! --

? 'Model S'
-- OK! --

>>> deneros_car.gas = 10
>>> deneros_car.drive()
? Tesla Model S goes vroom!
-- Not quite. Try again! --

? 'Tesla Model S goes vroom!'
-- OK! --

>>> deneros_car.drive()
? 'Cannot drive'
-- Not quite. Try again! --

? 'Cannot drive!'
-- OK! --

>>> deneros_car.fill_gas()
? 'Gas level: 20'
-- OK! --

>>> deneros_car.gas
? 20
-- OK! --

>>> Car.gas
? 30
-- OK! --

---------------------------------------------------------------------
Car > Suite 1 > Case 2
(cases remaining: 2)

What would Python display? If you get stuck, try it out in the Python
interpreter!

>>> from car import *
>>> deneros_car = Car('Tesla', 'Model S')
>>> deneros_car.wheels = 2
>>> deneros_car.wheels
? 2
-- OK! --

>>> Car.num_wheels
? 4
-- OK! --

>>> deneros_car.drive() # Type Error if an error occurs and Nothing if nothing is displayed
? 'Cannot drive'
-- Not quite. Try again! --

? 'Cannot drive!'
-- OK! --

>>> Car.drive() # Type Error if an error occurs and Nothing if nothing is displayed
? Nothing
-- Not quite. Try again! --

? Error
-- OK! --

>>> Car.drive(deneros_car) # Type Error if an error occurs and Nothing if nothing is displayed
? 'Cannot drive!'
-- OK! --

---------------------------------------------------------------------
Car > Suite 1 > Case 3
(cases remaining: 1)

What would Python display? If you get stuck, try it out in the Python
interpreter!

>>> from car import *
>>> deneros_car = MonsterTruck('Monster', 'Batmobile')
>>> deneros_car.drive() # Type Error if an error occurs and Nothing if nothing is displayed
(line 1)? Vroom! This Monster Truck is huge!
(line 2)? 'Monster Batmobile goes vroom!'
-- OK! --

>>> Car.drive(deneros_car) # Type Error if an error occurs and Nothing if nothing is displayed
? 'Monster Batmobile goes vroom!'
-- OK! --

>>> MonsterTruck.drive(deneros_car) # Type Error if an error occurs and Nothing if nothing is displayed
(line 1)? Vroom! This Monster Truck is huge!
(line 2)? 'Monster Batmobile goes vroom!'
-- OK! --

>>> Car.rev(deneros_car) # Type Error if an error occurs and Nothing if nothing is displayed
? Vroom! This Monster Truck is huge!
-- Not quite. Try again! --

? Error
-- OK! --

---------------------------------------------------------------------
OK! All cases for Car unlocked.

Parsons Problems

To work on these problems, open the Parsons editor:

1
python3 parsons

Q2: Cool Cats

The Cat class models a cat: you can find the implementation below. Now, you will implement NoisyCat; NoisyCats are very similar to Cats, but talks twice as much. However, in exchange for such great powers, it gives up one of its initial lives.

Use superclass methods wherever possible.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Cat:
def __init__(self, name, owner, lives=9):
self.is_alive = True
self.name = name
self.owner = owner
self.lives = lives

def talk(self):
return self.name + ' says meow!'

class NoisyCat(Cat):
"""
>>> my_cat = NoisyCat("Furball", "James")
>>> my_cat.name
'Furball'
>>> my_cat.is_alive
True
>>> my_cat.lives
8
>>> my_cat.talk()
'Furball says meow! Furball says meow!'
>>> friend_cat = NoisyCat("Tabby", "James", 2)
>>> friend_cat.talk()
'Tabby says meow! Tabby says meow!'
>>> friend_cat.lives
1
"""
def __init__(self, name, owner, lives=9):
Cat.__init__(self, name, owner, lives)
self.lives -= 1

def talk(self):
words = Cat.talk(self)
words = words + ' ' + words
return words

Coding Practice

Q3: Cat Adoption

So far, you’ve implemented the NoisyCat based off of the Cat class. However, you now want to be able to create lots of different Cats!

Build on the Cat class from the earlier problem by adding a class method called adopt_a_cat. This class method allows you to create Cats that can then be adopted.

Specifically, adopt_a_cat should return a new instance of a Cat whose owner is owner.

This Cat instance’s name and number of lives depends on the owner. Its name should be chosen from cat_names (provided in the skeleton code), and should correspond to the name at the index len(owner) % (modulo) the number of possible cat names. Its number of lives should be equal to len(owner) + the length of the chosen name.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Cat:
def __init__(self, name, owner, lives=9):
self.is_alive = True
self.name = name
self.owner = owner
self.lives = lives

def talk(self):
return self.name + ' says meow!'

@classmethod
def adopt_a_cat(cls, owner):
"""
Returns a new instance of a Cat.

This instance's owner is the given owner.
Its name and its number of lives is chosen programatically
based on the spec's noted behavior.

>>> cat1 = Cat.adopt_a_cat("Ifeoma")
>>> isinstance(cat1, Cat)
True
>>> cat1.owner
'Ifeoma'
>>> cat1.name
'Felix'
>>> cat1.lives
11
>>> cat2 = Cat.adopt_a_cat("Ay")
>>> cat2.owner
'Ay'
>>> cat2.name
'Grumpy'
>>> cat2.lives
8
"""
cat_names = ["Felix", "Bugs", "Grumpy"]
"*** YOUR CODE HERE ***"
cat_name = cat_names[len(owner) % 3]
lives = len(owner) + len(cat_name)
return cls(cat_name, owner, lives)

Use Ok to test your code:

1
python3 ok -q Cat.adopt_a_cat✂️

Accounts

Let’s say we’d like to model a bank account that can handle interactions such as depositing funds or gaining interest on current funds. In the following questions, we will be building off of the Account class. Here’s our current definition of the class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Account:
"""An account has a balance and a holder.
>>> a = Account('John')
>>> a.deposit(10)
10
>>> a.balance
10
>>> a.interest
0.02
>>> a.time_to_retire(10.25) # 10 -> 10.2 -> 10.404
2
>>> a.balance # balance should not change
10
>>> a.time_to_retire(11) # 10 -> 10.2 -> ... -> 11.040808032
5
>>> a.time_to_retire(100)
117
"""
max_withdrawal = 10
interest = 0.02

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

def deposit(self, amount):
self.balance = self.balance + amount
return self.balance

def withdraw(self, amount):
if amount > self.balance:
return "Insufficient funds"
if amount > self.max_withdrawal:
return "Can't withdraw that amount"
self.balance = self.balance - amount
return self.balance

Q4: Retirement

Add a time_to_retire method to the Account class. This method takes in an amount and returns how many years the holder would need to wait in order for the current balance to grow to at least amount, assuming that the bank adds balance times the interest rate to the total balance at the end of every year.

1
2
3
4
5
6
7
8
9
10
def time_to_retire(self, amount):
"""Return the number of years until balance would grow to amount."""
assert self.balance > 0 and amount > 0 and self.interest > 0
"*** YOUR CODE HERE ***"
cnt = 0
deposit = self.balance * ((1 + self.interest) ** cnt)
while deposit < amount:
cnt = cnt + 1
deposit = self.balance * ((1 + self.interest) ** cnt)
return cnt

Use Ok to test your code:

1
python3 ok -q Account✂️

Q5: FreeChecking

Implement the FreeChecking class, which is like the Account class from lecture except that it charges a withdraw fee after 2 withdrawals. If a withdrawal is unsuccessful, it still counts towards the number of free withdrawals remaining, but no fee for the withdrawal will be charged.

Hint: Don’t forget that FreeChecking inherits from Account! Check the Inheritance section in Topics for a refresher.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class FreeChecking(Account):
"""A bank account that charges for withdrawals, but the first two are free!
>>> ch = FreeChecking('Jack')
>>> ch.balance = 20
>>> ch.withdraw(100) # First one's free
'Insufficient funds'
>>> ch.withdraw(3) # And the second
17
>>> ch.balance
17
>>> ch.withdraw(3) # Ok, two free withdrawals is enough
13
>>> ch.withdraw(3)
9
>>> ch2 = FreeChecking('John')
>>> ch2.balance = 10
>>> ch2.withdraw(3) # No fee
7
>>> ch.withdraw(3) # ch still charges a fee
5
>>> ch.withdraw(5) # Not enough to cover fee + withdraw
'Insufficient funds'
"""
withdraw_fee = 1
free_withdrawals = 2

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

def withdraw(self, amount):
if amount + self.withdraw_fee > self.balance:
return "Insufficient funds"
if amount > self.max_withdrawal:
return "Can't withdraw that amount"


if self.free_withdrawals > 0:
self.free_withdrawals -= 1

self.balance = self.balance - amount
if self.free_withdrawals <= 0:
self.balance = self.balance - self.withdraw_fee

return self.balance

Use Ok to test your code:

1
python3 ok -q FreeChecking✂️

Submit

Make sure to submit this assignment by running:

1
python3 ok --submit