We've provided a set of starter files with skeleton code for the exercises in the lab. You can get them in the following places:
Consider the following function:
def make_counter():
"""Makes a counter function.
>>> counter = make_counter()
>>> counter()
1
>>> counter()
2
"""
count = 0
def counter():
count = count + 1
return count
return counter
Try running this function's doctests. You'll find that it causes the following error:
UnboundLocalError: local variable 'count' referenced before assignment
Why does this happen? Normally, when we create variables (like count
= ...
in counter
), we create the variable in the local frame. Thus
count
is marked as a local variable in the counter
function.
However, notice that we tried to compute count + 1
before the local
variable was created! That's why we get the UnboundLocalError
.
To avoid this problem, we introduce the nonlocal
keyword. It allows
us to update a variable in a parent frame. Consider this improved
example:
def make_counter():
"""Makes a counter function.
>>> counter = make_counter()
>>> counter()
1
>>> counter()
2
"""
count = 0
def counter():
nonlocal count
count = count + 1
return count
return counter
Notice the nonlocal count
. This declares the count
variable as a
nonlocal variable, so now we can update count
.
Predict what Python will display when the following lines are typed into the interpreter:
>>> def make_funny_adder(n):
... def adder(x):
... if x == 'new':
... nonlocal n
... n = n + 1
... else:
... return x + n
... return adder
>>> h = make_funny_adder(3)
>>> h(5)
______
>>> j = make_funny_adder(7)
>>> j(5)
______
>>> h('new')
>>> h(5)
______
Write a function make_fib
that returns a function that reurns the
next Fibonacci number each time it is called.
def make_fib():
"""Returns a function that returns the next Fibonacci number
every time it is called.
>>> fib = make_fib()
>>> fib()
0
>>> fib()
1
>>> fib()
1
>>> fib()
2
>>> fib()
3
"""
"*** YOUR CODE HERE ***"
Recall make_test_dice
from the Hog project. make_test_dice
takes
in a sequence of numbers and returns a zero-argument function. This
zero-argument function will cycle through the list, returning one
element from the list every time. Implement make_test_dice
.
def make_test_dice(seq):
"""Makes deterministic dice.
>>> dice = make_test_dice([2, 6, 1])
>>> dice()
2
>>> dice()
6
>>> dice()
1
>>> dice()
2
>>> other = make_test_dice([1])
>>> other()
1
>>> dice()
6
"""
"*** YOUR CODE HERE ***"
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
______
>>> a.holder
______
>>> 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!")
...
>>> c = CheckingAccount("Eric")
>>> a.deposit(30)
______
>>> c.deposit(30)
______
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. 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"
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
:
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!)
Here are the Account
and CheckingAccount
classes from lecture:
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!
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.