# 61A Homework 7

Due by 11:59pm on Wednesday, 3/13

Submission. See the online submission instructions. We have provided a starter file for the questions below.

Readings. Section 2.5 of the online lecture notes.

Q1. In lecture, we implemented a mutable rlist that supports push and pop operations. We further claimed that arbitrary list mutation, such as setitem, can be implemented in terms of push and pop. Proceed to implement the setitem behavior below and make any necessary changes to the dispatch function. Your solution should only push and pop, calling the dispatch function to do so. Do not manipulate contents directly, and do not create a new mutable rlist.

```# Mutable rlist
def mutable_rlist():
"""A mutable rlist that supports push, pop, and setitem operations.

>>> a = mutable_rlist()
>>> a('push', 3)
>>> a('push', 2)
>>> a('push', 1)
>>> a('setitem', 1, 4)
>>> a('str')
'<rlist (1, 4, 3)>'
"""
contents = empty_rlist

def setitem(index, value):

def dispatch(message, value=None):
nonlocal contents
if message == 'first':
return first(contents)
if message == 'rest':
return rest(contents)
if message == 'len':
return len_rlist(contents)
if message == 'getitem':
return getitem_rlist(contents, value)
if message == 'str':
return str_rlist(contents)
if message == 'pop':
item = first(contents)
contents = rest(contents)
return item
if message == 'push':
contents = rlist(value, contents)

return dispatch

def pair(x, y):
def dispatch(m):
if m == 0:
return x
elif m == 1:
return y
return dispatch

empty_rlist = None

def rlist(first, rest):
return pair(first, rest)

def first(s):
return s(0)

def rest(s):
return s(1)

def len_rlist(s):
if s == empty_rlist:
return 0
return 1 + len_rlist(rest(s))

def getitem_rlist(s, k):
if k == 0:
return first(s)
return getitem_rlist(rest(s), k - 1)

def rlist_to_tuple(s):
if s == empty_rlist:
return ()
return (first(s),) + rlist_to_tuple(rest(s))

def str_rlist(s):
return '<rlist ' + str(rlist_to_tuple(s)) + '>'
```

Q2. Create a class called VendingMachine that represents a vending machine for some product. A VendingMachine object doesn't actually return anything but strings describing its interactions. See the doctest below for examples.

In Nanjing, there are even vending machines for crabs.

```class VendingMachine(object):
"""A vending machine that vends some product for some price.

>>> v = VendingMachine('crab', 10)
>>> v.vend()
'Machine is out of stock.'
>>> v.restock(2)
'Current crab stock: 2'
>>> v.vend()
'You must deposit \$10 more.'
>>> v.deposit(7)
'Current balance: \$7'
>>> v.vend()
'You must deposit \$3 more.'
>>> v.deposit(5)
'Current balance: \$12'
>>> v.vend()
'Here is your crab and \$2 change.'
>>> v.deposit(10)
'Current balance: \$10'
>>> v.vend()
>>> v.deposit(15)
'Machine is out of stock. Here is your \$15.'
"""
```

Q3. Create a class called MissManners that promotes politeness among our objects. A MissManners object takes another object on construction. It has one method, called ask. It responds by calling methods on the object it contains, but only if the caller said please. The doctest gives an example.

Hint: Your implementation will need to use the *args notation that allows functions to take a flexible number of arguments. You may also find the hasattr function useful.

```class MissManners(object):
"""A container class that only forward messages that say please.

>>> v = VendingMachine('teaspoon', 10)
>>> v.restock(2)
'Current teaspoon stock: 2'
>>> m = MissManners(v)
'You must learn to say please.'
'You must deposit \$10 more.'
'Current balance: \$20'
'You must learn to say please.'
'Thanks for asking, but I know not how to give up a teaspoon'
'Here is your teaspoon and \$10 change.'
"""
```

Make sure to obey the following restrictions:

1. Do not modify Account, but instead create a subclass SecureAccount.
2. Do not actually modify the account balances with methods in SecureAccount. Instead, arrange that the methods of Account continue to handle that.
```class Account(object):
"""A bank account that allows deposits and withdrawals.

>>> john = Account('John')
>>> jack = Account('Jack')
>>> john.deposit(10)
10
>>> john.deposit(5)
15
>>> john.interest
0.02
>>> jack.deposit(7)
7
>>> jack.deposit(5)
12
"""

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

```

The following code tests your implementation using the unittest module, Python's built-in unit testing framework. This module provides greater flexibility and generality than doctest. Read the online documentation to learn more about the features it provides. You can run the tests with the command python3 -m unittest -v hw7.py.

```import unittest

class SecureAccountTest(unittest.TestCase):
"""Test the SecureAccount class."""

def setUp(self):
self.account = SecureAccount('Alyssa P. Hacker', 'p4ssw0rd')

def test_secure(self):
acc = self.account
acc.deposit(1000)
self.assertEqual(acc.balance, 1000, 'Bank error! Incorrect balance')
self.assertEqual(acc.withdraw(100),
'This account requires a password to withdraw')
self.assertEqual(acc.secure_withdraw(100, 'p4ssw0rd'), 900,
"Didn't withdraw 100")
self.assertEqual(acc.secure_withdraw(100, 'p4ssw0rd'),
'This account is locked')
self.assertEqual(acc.balance, 900, 'Withdrew from locked account')
```

Q5. (Extra for Experts) Our SecureAccount implementation still isn't quite secure, since anyone can read the password stored by an account. Modify the SecureAccount class to prevent this. Also ensure that no one can make more than three bad guesses in a row at the password (e.g. don't allow security to be bypassed by modifying a counter).

Hint: Higher-order functions may help. (You don't have to worry about inspection of closures for this exercise.)

```"*** YOUR CODE HERE ***"

class MoreSecureAccountTest(SecureAccountTest):
"""Test the MoreSecureAccount class."""

def setUp(self):
self.account = MoreSecureAccount('Alyssa P. Hacker', 'p4ssw0rd')
```