Homework 7: OOP and Inheritance
Due by 11:59pm on Wednesday, October 29
Instructions
Download hw07.zip. Inside the archive, you will find a file called
hw07.py, along with a copy of the ok autograder.
Submission: When you are done, submit the assignment to Gradescope. You may submit more than once before the deadline; only the final submission will be scored. Check that you have successfully submitted your code on Gradescope. See Lab 0 for more instructions on submitting assignments.
Using Ok: If you have any questions about using Ok, please refer to this guide.
Readings: You might find the following references useful:
Grading: Homework is graded based on correctness. Each incorrect problem will decrease the total score by one point. This homework is out of 6 points.
Required Questions
Getting Started Videos
These videos may provide some helpful direction for tackling the coding problems on this assignment.
To see these videos, you should be logged into your berkeley.edu email.
Q1: Email
An email system has three classes: Email, Server, and Client. A Client can
compose an email, which it will send to the Server. The Server then delivers it to the
inbox of another Client. To achieve this, a Server has a dictionary called
clients that maps the name of the Client to the Client instance.
Assume that a Client never changes the Server that it uses, and it can only compose an Email using that Server.
Fill in the definitions below to finish the implementation! The Email class
has been completed for you.
Important: Before you start, make sure you read the entire code snippet to understand the relationships between the classes, and pay attention to the parameter type of the methods. Think about what variables you have access to in each method and how can you use them to access the other classes and their methods.
Note:
- The
senderparameter from the__init__(self, msg, sender, recipient_name)method in theEmailclass is aClientinstance. - The
clientparameter from theregister_client(self, client)method in theServerclass is aClientinstance. - The
emailparameter from thesend(self, email)method in theServerclass is anEmailinstance.
class Email:
"""An email has the following instance attributes:
msg (str): the contents of the message
sender (Client): the client that sent the email
recipient_name (str): the name of the recipient (another client)
"""
def __init__(self, msg: str, sender, recipient_name: str):
self.msg = msg
self.sender = sender
self.recipient_name = recipient_name
class Server:
"""Each Server has one instance attribute called clients that is a
dictionary from client names to client objects.
>>> s = Server()
>>> # Dummy client class implementation for testing only
>>> class Client:
... def __init__(self, server, name):
... self.inbox = []
... self.server = server
... self.name = name
>>> a = Client(s, 'Alice')
>>> b = Client(s, 'Bob')
>>> s.register_client(a)
>>> s.register_client(b)
>>> len(s.clients) # we have registered 2 clients
2
>>> all([type(c) == str for c in s.clients.keys()]) # The keys in self.clients should be strings
True
>>> all([type(c) == Client for c in s.clients.values()]) # The values in self.clients should be Client instances
True
>>> new_a = Client(s, 'Alice') # a new client with the same name as an existing client
>>> s.register_client(new_a)
>>> len(s.clients) # the key of a dictionary must be unique
2
>>> s.clients['Alice'] is new_a # the value for key 'Alice' should now be updated to the new client new_a
True
>>> e = Email("I love 61A", b, 'Alice')
>>> s.send(e)
>>> len(new_a.inbox) # one email has been sent to new Alice
1
>>> type(new_a.inbox[0]) == Email # a Client's inbox is a list of Email instances
True
"""
def __init__(self):
self.clients = {}
def send(self, email: Email):
"""Append the email to the inbox of the client it is addressed to.
email is an instance of the Email class.
"""
____.inbox.append(email)
def register_client(self, client):
"""Add a client to the clients mapping (which is a
dictionary from client names to client instances).
client is an instance of the Client class.
"""
____[____] = ____
class Client:
"""A client has a server, a name (str), and an inbox (list).
>>> s = Server()
>>> a = Client(s, 'Alice')
>>> b = Client(s, 'Bob')
>>> a.compose('Hello, World!', 'Bob')
>>> b.inbox[0].msg
'Hello, World!'
>>> a.compose('CS 61A Rocks!', 'Bob')
>>> len(b.inbox)
2
>>> b.inbox[1].msg
'CS 61A Rocks!'
>>> b.inbox[1].sender.name
'Alice'
"""
def __init__(self, server: Server, name: str):
self.inbox: list = []
self.server = server
self.name = name
server.register_client(____)
def compose(self, message: str, recipient_name: str):
"""Send an email with the given message to the recipient."""
email = Email(message, ____, ____)
self.server.send(email)
There are two
oktests for this problem. Both tests must pass in order to receive full credit. You should run them in the order they are presented. Thepython3 ok -q Servertest will test theServerclass implementation, and it does not require a correct implementation of theClientclass. Thepython3 ok -q Clienttest will test both theServerandClientclass implementations. So, make sure to test yourServerclass implementation first before testing theClientclass.
Use Ok to test your code:
python3 ok -q Server
python3 ok -q Client
Q2: Vending Machine
In this question you'll create a vending machine that sells a single product and provides change when needed.
Implement the VendingMachine class, which models a vending machine for one specific product.
The methods of a VendingMachine object return strings to describe the machine’s status and operations.
Ensure that your output matches exactly with the strings provided in the doctests, including punctuation and spacing.
You may find Python's formatted string literals, or f-strings useful. A quick example:
>>> feeling = 'love' >>> course = 'Data C88C!' >>> combined_string = f'I {feeling} {course}' >>> combined_string 'I love Data C88C!'
class VendingMachine:
"""A vending machine that vends some product for some price.
>>> v = VendingMachine('candy', 10)
>>> v.vend()
'Nothing left to vend. Please restock.'
>>> v.add_funds(15)
'Nothing left to vend. Please restock. Here is your $15.'
>>> v.restock(2)
'Current candy stock: 2'
>>> v.vend()
'Please add $10 more funds.'
>>> v.add_funds(7)
'Current balance: $7'
>>> v.vend()
'Please add $3 more funds.'
>>> v.add_funds(5)
'Current balance: $12'
>>> v.vend()
'Here is your candy and $2 change.'
>>> v.add_funds(10)
'Current balance: $10'
>>> v.vend()
'Here is your candy.'
>>> v.add_funds(15)
'Nothing left to vend. Please restock. Here is your $15.'
>>> w = VendingMachine('soda', 2)
>>> w.restock(3)
'Current soda stock: 3'
>>> w.restock(3)
'Current soda stock: 6'
>>> w.add_funds(2)
'Current balance: $2'
>>> w.vend()
'Here is your soda.'
"""
def __init__(self, product: str, price: int):
"""Set the product and its price, as well as other instance attributes."""
"*** YOUR CODE HERE ***"
def restock(self, n: int) -> str:
"""Add n to the stock and return a message about the updated stock level.
E.g., Current candy stock: 3
"""
"*** YOUR CODE HERE ***"
def add_funds(self, n: int) -> str:
"""If the machine is out of stock, return a message informing the user to restock
(and return their n dollars).
E.g., Nothing left to vend. Please restock. Here is your $4.
Otherwise, add n to the balance and return a message about the updated balance.
E.g., Current balance: $4
"""
"*** YOUR CODE HERE ***"
def vend(self) -> str:
"""Dispense the product if there is sufficient stock and funds and
return a message. Update the stock and balance accordingly.
E.g., Here is your candy and $2 change.
If not, return a message suggesting how to correct the problem.
E.g., Nothing left to vend. Please restock.
Please add $3 more funds.
"""
"*** YOUR CODE HERE ***"
Use Ok to test your code:
python3 ok -q VendingMachine
Checking Accounts
Let's improve the Account class from lecture, which models a bank account
that can process deposits and withdrawals.
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 # Calling time_to_retire method should not change the balance
10
>>> a.time_to_retire(11) # 10 -> 10.2 -> ... -> 11.040808032
5
>>> a.time_to_retire(100)
117
"""
max_withdrawal: int = 10
interest: float = 0.02
def __init__(self, account_holder: str):
self.balance = 0
self.holder = account_holder
def deposit(self, amount: int) -> int:
self.balance = self.balance + amount
return self.balance
def withdraw(self, amount: int) -> int | str:
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
Q3: Retirement
Add a time_to_retire method to the Account class. This method takes in an
amount and returns the number of years until the current balance grows to at
least amount, assuming that the bank adds the interest (calculated as the
current balance multiplied by the interest rate) to the balance at the end
of each year. Make sure you're not modifying the account's balance!
Important: Calling the
time_to_retiremethod should not change the account balance.
def time_to_retire(self, amount: float) -> int:
"""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 ***"
Use Ok to test your code:
python3 ok -q Account
Q4: FreeChecking
Implement the FreeChecking class, which is like the Account class
except that it charges a withdraw fee withdraw_fee after
withdrawing free_withdrawals number of times.
If a withdrawal is unsuccessful, no withdrawal fee will be charged, but it still counts towards the number of free
withdrawals remaining.
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. Still counts as a free withdrawal even though it was unsuccessful
'Insufficient funds'
>>> ch.withdraw(3) # Second withdrawal is also free
17
>>> ch.balance
17
>>> ch.withdraw(3) # Now there is a fee because free_withdrawals is only 2
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: int = 1
free_withdrawals: int = 2
"*** YOUR CODE HERE ***"
Use Ok to test your code:
python3 ok -q FreeChecking
Check Your Score Locally
You can locally check your score on each question of this assignment by running
python3 ok --score
This does NOT submit the assignment! When you are satisfied with your score, submit the assignment to Gradescope to receive credit for it.
Submit Assignment
Submit this assignment by uploading any files you've edited to the appropriate Gradescope assignment. Lab 00 has detailed instructions.