Classes Answers
Argument Unpacking Exercises
Multi-valued Dictionary
This is the dict_from_tuple exercise in dictionaries.py. Edit the dictionaries.py file in the exercises directory to implement this exercise. To test it, run python test.py dict_from_tuple in your exercises directory.
Edit the function dict_from_tuple and modify it to accept a list of tuples of any length and return a dictionary which uses the first item of each tuple as keys and all subsequent items as values.
Note
This exercise is different from (but similar to) dict_from_truple, which you may have seen earlier.
Example usage:
>>> from dictionaries import dict_from_tuple
>>> dict_from_tuple([(1, 2, 3, 4), (5, 6, 7, 8)])
{1: (2, 3, 4), 5: (6, 7, 8)}
>>> dict_from_tuple([(1, 2, 3), (4, 5, 6), (7, 8, 9)])
{1: (2, 3), 4: (5, 6), 7: (8, 9)}
Answers
Using slicing:
def dict_from_tuple(input_list):
"""Turn multi-item tuples into a dictionary of multi-valued tuples."""
new_dict = {}
for items in input_list:
new_dict[items[0]] = items[1:]
return new_dict
Using tuple unpacking and * instead of slicing:
def dict_from_tuple(input_list):
"""Turn multi-item tuples into a dictionary of multi-valued tuples."""
new_dict = {}
for key, *values in input_list:
new_dict[key] = tuple(values)
return new_dict
Transpose
This is the transpose exercise in stars.py. Edit the stars.py file in the exercises directory to implement this exercise. To test it, run python test.py transpose in your exercises directory.
Make a function transpose that accepts a list of lists and returns the transpose of the list of lists.
Example usage:
>>> from stars import transpose
>>> transpose([[1, 2], [3, 4]])
[[1, 3], [2, 4]]
>>> matrix = [['a','b','c'],['d','e','f'],['g','h','i']]
>>> transpose(matrix)
[['a', 'd', 'g'], ['b', 'e', 'h'], ['c', 'f', 'i']]
Answers
def transpose(matrix):
"""Return a transposed version of given list of lists."""
transposed = []
for row in zip(*matrix):
transposed.append(list(row))
return transposed
Sum Each
Edit the sum_each function in the stars.py file so that it accepts any number of tuples of numbers and returns tuple of the sums of numbers in corresponding positions (sum of all first items, sum of all second items, etc.)
Example usage:
>>> from stars import sum_each
>>> sum_each((1, 2), (4, 5))
(5, 7)
>>> sum_each((1, 2), (4, 5), (7, 8), (1, 1))
(13, 16)
>>> sum_each((1, 2, 3), (4, 5, 6), (7, 8, 9), (1, 1, 0))
(13, 16, 18)
Answers
Making new tuples by adding one item on the end each time:
def sum_each(*number_tuples):
"""Return tuple with sums of each number in given tuples."""
sums = ()
for numbers in zip(*number_tuples):
sums += (sum(numbers),)
return sums
Maybe a bit more readable (that trailing comma is odd):
def sum_each(*number_tuples):
"""Return tuple with sums of each number in given tuples."""
sums = ()
for numbers in zip(*number_tuples):
sums = (*sums, sum(numbers))
return sums
Using a list and then converting it to a tuple afterward:
def sum_each(*number_tuples):
"""Return tuple with sums of each number in given tuples."""
sums = []
for numbers in zip(*number_tuples):
sums.append(sum(numbers))
return tuple(sums)
HTML Tag
This is the html_tag exercise in stars.py. Edit the stars.py file in the exercises directory to implement this exercise. To test it, run python test.py html_tag in your exercises directory.
Make a function that accepts a positional argument and keyword arguments and generates an HTML tag using them.
Example:
>>> from stars import html_tag
>>> html_tag("input", type="email", name="email", placeholder="E-mail address")
'<input type="email" name="email" placeholder="E-mail address">'
>>> img_tag = html_tag("img", src="https://placehold.it/10x10", alt="Placeholder")
>>> img_tag
'<img src="https://placehold.it/10x10" alt="Placeholder">'
Answers
Manual string building:
def html_tag(cmd, **kwargs):
"""Make an HTML tag from the given tag name and attributes."""
tag = '<' + cmd
for param, value in kwargs.items():
tag += ' ' + param + '="' + value + '"'
tag += '>'
return tag
Building string with format and join (more idiomatic):
def html_tag(tag_name, **attributes):
"""Make an HTML tag from the given tag name and attributes."""
attr_list = []
for param, value in attributes.items():
attr_list.append('{}="{}"'.format(param, value))
return "<{tag} {attrs}>".format(tag=tag_name, attrs=" ".join(attr_list))
With f-strings:
def html_tag(tag_name, **attributes):
attr_list = []
for param, value in attributes.items():
attr_list.append(f'{param}="{value}"')
return f"<{tag_name} {' '.join(attr_list)}>"
Total Length
This is the total_length exercise in iteration.py.
Make a function total length that should calculate the total length of all given iterables.
This function should even work for iterables that don’t work with the built-in len (like zip, enumerate, etc.).
Example:
>>> from iteration import total_length
>>> total_length([1, 2, 3])
3
>>> total_length()
0
>>> total_length([1, 2, 3], [4, 5], iter([6, 7]))
7
Answers
def total_length(*iterables):
total = 0
for iterable in iterables:
for item in iterable:
total += 1
return total
Class Exercises
Bank Account
This is the BankAccount exercise in classes.py.
To test it, run python test.py BankAccount from the exercises directory.
Make a BankAccount class that allows depositing and withdrawing money:
>>> from classes import BankAccount
>>> trey_account = BankAccount(20)
>>> trey_account.balance
20
>>> trey_account.deposit(100)
>>> trey_account.balance
120
>>> trey_account.withdraw(40)
>>> trey_account.balance
80
>>> trey_account
BankAccount(balance=80)
Your BankAccount class should also support transfers:
>>> mary_account = BankAccount(100)
>>> dana_account = BankAccount()
>>> mary_account.transfer(dana_account, 20)
>>> mary_account.balance
80
>>> dana_account.balance
20
Answers
class BankAccount:
"""Bank account including an account balance."""
def __init__(self, balance=0):
self.balance = balance
def __repr__(self):
return f"{type(self).__name__}(balance={self.balance})"
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
self.balance -= amount
def transfer(self, other_account, amount):
self.withdraw(amount)
other_account.deposit(amount)
Record Transactions
This is the BankAccount exercise in classes.py.
To test it, run python test.py BankAccount from the exercises directory.
Note
To test these changes, you need to modify the BankAccountTests class in classes_test.py to comment out the @unittest.skip lines with “Comment this line for transactions exercise.”.
Modify our class to record transactions for deposits and withdrawals.
A transactions attribute should be added to our object that maintains a list of all transactions in our account. Each transaction should be a tuple containing an action name (as a string), the amount the account changed, and the account balance after the change.
Example usage:
>>> from classes import BankAccount
>>> my_account = BankAccount(10)
>>> my_account.deposit(100)
>>> my_account.withdraw(40)
>>> my_account.deposit(95)
>>> my_account.transactions
[('OPEN', 10, 10), ('DEPOSIT', 100, 110), ('WITHDRAWAL', -40, 70), ('DEPOSIT', 95, 165)]
Answers
class BankAccount:
"""Bank account that records transactions."""
def __init__(self, balance=0):
self.balance = balance
self.transactions = [("OPEN", balance, balance)]
def __repr__(self):
return f"{type(self).__name__}(balance={self.balance})"
def deposit(self, amount):
self.balance += amount
self.transactions.append(("DEPOSIT", amount, self.balance))
def withdraw(self, amount):
self.balance -= amount
self.transactions.append(("WITHDRAWAL", -amount, self.balance))
def transfer(self, other_account, amount):
self.withdraw(amount)
other_account.deposit(amount)
Month
This is the Month exercise in classes.py.
Edit the classes.py file in the exercises directory to implement this exercise.
To test it, run python test.py Month from the exercises directory.
Make a Month class that has year and month attributes.
This class should have two string representations and a first method that returns a datetime.date object representing the first day of the given month.
Example:
>>> from classes import Month
>>> python2_eol_month = Month(year=2020, month=1)
>>> python2_eol_month
Month(2020, 1)
>>> str(python2_eol_month)
'2020-01'
>>> print(python2_eol_month)
2020-01
>>> python2_eol_month.first()
datetime.date(2020, 1, 1)
Answers
from datetime import date
class Month:
"""Class representing an entire month."""
def __init__(self, year, month):
self.year, self.month = year, month
def __repr__(self):
return f"{type(self).__name__}({self.year}, {self.month})"
def __str__(self):
return f"{self.year}-{self.month:02d}"
def first(self):
return date(self.year, self.month, 1)
Row
This is the Row exercise in classes.py.
Edit the classes.py file in the exercises directory to implement this exercise.
To test it, run python test.py Row from the exercises directory.
Make a Row class that accepts any keyword arguments given to it and stores these arguments as attributes.
>>> from classes import Row
>>> row = Row(a=1, b=2)
>>> row.a
1
>>> row.b
2
Answers
class Row:
"""Row class that stores all given arguments as attributes."""
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
def __repr__(self):
attrs = ", ".join(
f"{key}={value}"
for key, value in self.__dict__.items()
)
return f"{type(self).__name__}({attrs})"
Or using __dict__ to initialize and to get a nice string representation:
class Row:
"""Row class that stores all given arguments as attributes."""
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def __repr__(self):
attrs = ", ".join(
f"{key}={value}"
for key, value in self.__dict__.items()
)
return f"{type(self).__name__}({attrs})"
Ice Cream Flavor
This is the Flavor exercise in classes.py.
Edit the classes.py file in the exercises directory to implement this exercise.
To test it, run python test.py Flavor from the exercises directory.
Make a Flavor class that has name, ingredients, and has_dairy attributes.
The name attribute should be a required string.
The ingredients attribute should be an optional iterable.
The has_dairy attribute should be an optional boolean value (defaulting to True).
Instances of the Flavor class should also have a nice string representation.
It should work like this:
>>> from classes import Flavor
>>> vanilla = Flavor("vanilla")
>>> vanilla.name
'vanilla'
>>> vanilla.has_dairy
True
>>> vanilla.ingredients
[]
>>> vanilla
Flavor(name='vanilla', ingredients=[], has_dairy=True)
>>> mint_chip = Flavor(
... name="mint chip",
... ingredients=["sugar", "mint", "cashews", "coconut milk"],
... has_dairy=False,
... )
>>> mint_chip.ingredients
['sugar', 'mint', 'cashews', 'coconut milk']
>>> mint_chip.has_dairy
False
Answers
class Flavor:
"""Flavor of ice cream."""
def __init__(self, name, ingredients=[], has_dairy=True):
self.name = name
self.ingredients = list(ingredients)
self.has_dairy = has_dairy
def __repr__(self):
return (
f"{type(self).__name__}(name={repr(self.name)},"
f" ingredients={self.ingredients},"
f" has_dairy={self.has_dairy})"
)
Ice Cream Size
This is the Size exercise in classes.py.
Edit the classes.py file in the exercises directory to implement this exercise.
To test it, run python test.py Size from the exercises directory.
Make a Size class that has quantity, unit, and price attributes.
Instances of the Size class should also have two string representations (as shown below).
It should work like this:
>>> from classes import Size
>>> one_quart = Size(quantity=1, unit="quart", price="$9")
>>> one_quart
Size(quantity=1, unit='quart', price='$9')
>>> print(one_quart)
1 quart
>>> two_scoops = Size(quantity=2, unit="scoop", price="$3")
>>> two_scoops
Size(quantity=2, unit='scoop', price='$3')
>>> print(two_scoops)
2 scoops
Answers
class Size:
"""Ice cream size."""
def __init__(self, quantity, unit, price):
self.quantity = quantity
self.unit = unit
self.price = price
def __str__(self):
units = self.unit if self.quantity == 1 else f"{self.unit}s"
return f"{self.quantity} {units}"
def __repr__(self):
return (
f"{type(self).__name__}(quantity={repr(self.quantity)},"
f" unit={repr(self.unit)}, price={repr(self.price)})"
)
Ice Cream
This is the IceCream exercise in classes.py.
Edit the classes.py file in the exercises directory to implement this exercise.
To test it, run python test.py IceCream from the exercises directory.
Make an IceCream class that has flavor and size attributes.
It should work like this:
>>> from classes import IceCream
>>> one_quart = Size(quantity=1, unit="quart", price="$9")
>>> vanilla = Flavor("vanilla")
>>> quart_of_vanilla = IceCream(flavor=vanilla, size=one_quart)
>>> print(quart_of_vanilla)
1 quart of vanilla
>>> print(IceCream(flavor=vanilla, size=Size(quantity=2, unit="scoop", price="$3")))
2 scoops of vanilla
Answers
class Flavor:
"""Flavor of ice cream."""
def __init__(self, name, ingredients=[], has_dairy=True):
self.name = name
self.ingredients = list(ingredients)
self.has_dairy = has_dairy
def __repr__(self):
return (
f"{type(self).__name__}(name={repr(self.name)},"
f" ingredients={self.ingredients},"
f" has_dairy={self.has_dairy})"
)
class Size:
"""Ice cream size."""
def __init__(self, quantity, unit, price):
self.quantity = quantity
self.unit = unit
self.price = price
def __str__(self):
units = self.unit if self.quantity == 1 else f"{self.unit}s"
return f"{self.quantity} {units}"
def __repr__(self):
return (
f"{type(self).__name__}(quantity={repr(self.quantity)},"
f" unit={repr(self.unit)}, price={repr(self.price)})"
)
class IceCream:
"""Ice cream to be ordered in our ice cream shop."""
def __init__(self, flavor, size):
self.flavor = flavor
self.size = size
def __str__(self):
return f"{self.size} of {self.flavor.name}"
Email Server
This is the IMAPChecker exercise in classes.py.
The below code includes a get_connection function which returns a mail server object and three other functions that accept a mail server object as their first argument.
from email.parser import Parser
from imaplib import IMAP4_SSL
def get_connection(host, username, password):
"""Initialize IMAP server and login"""
server = IMAP4_SSL(host)
server.login(username, password)
server.select("inbox")
return server
def close_connection(server):
server.close()
server.logout()
def get_message_uids(server):
"""Return unique identifiers for each message"""
return server.uid("search", None, "ALL")[1][0].split()
def get_message(server, uid):
"""Get email message identified by given UID"""
result, data = server.uid("fetch", uid, "(RFC822)")
(_, message_text), _ = data
message = Parser().parsestr(message_text)
return message
Here’s an example usage of that code:
server = get_connection(host, username, password)
messages = [
message
for uid in get_message_uids(server)
]
close_connection(server)
I’d like you to refactor that code to use a class instead. It should work just like this:
server = IMAPChecker(host)
server.authenticate(username, password)
messages = [
message
for uid in server.get_message_uids()
]
server.quit()
Answers
from email.parser import Parser
from imaplib import IMAP4_SSL
class IMAPChecker:
def __init__(self, host):
"""Initialize IMAP email server with given host"""
self.server = IMAP4_SSL(host)
def authenticate(self, username, password):
"""Authenticate with email server"""
self.server.login(username, password)
self.server.select("inbox")
def quit(self):
self.server.close()
self.server.logout()
def get_message_uids(self):
"""Return unique identifiers for each message"""
return self.server.uid("search", None, "ALL")[1][0].split()
def get_message(self, uid):
"""Get email message identified by given UID"""
result, data = self.server.uid("fetch", uid, "(RFC822)")
(_, message_text), _ = data
message = Parser().parsestr(message_text)
return message
Dataclass Exercises
Ice Cream DataClasses
Rewrite the Size, Flavor, and IceCream classes in classes.py using data classes.
You can test each of these classes by running tests for Size, Flavor, and IceCream.
Answers
from dataclasses import dataclass, field
@dataclass
class Flavor:
"""Flavor of ice cream."""
name: str
ingredients: list = field(default_factory=tuple)
has_dairy: bool = True
def __post_init__(self):
"""Copy given ingredients iterable."""
self.ingredients = list(self.ingredients)
@dataclass
class Size:
"""Ice cream size."""
quantity: int
unit: str
price: str
def __str__(self):
units = self.unit if self.quantity == 1 else f"{self.unit}s"
return f"{self.quantity} {units}"
@dataclass
class IceCream:
"""Ice cream to be ordered in our ice cream shop."""
flavor: Flavor
size: Size
def __str__(self):
return f"{self.size} of {self.flavor.name}"
Inheritance Exercises
Minimum Balance
This is the MinimumBalanceAccount exercise in classes.py.
Create a class MinimumBalanceAccount which subclasses BankAccount. This new class should raise an exception whenever the user attempts to withdraw so much money that their account goes below 0.
>>> from bank_account import MinimumBalanceAccount
>>> my_account = MinimumBalanceAccount()
Account opened.
Account balance is $0.
>>> my_account.deposit(100)
$100 deposited.
Account balance is $100.
>>> my_account.withdraw(200)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "bank_account.py", line 45, in withdraw
raise ValueError("Balance cannot be less than $0")
ValueError: Balance cannot be less than $0
Answers
class BankAccount:
"""Base Bank account including an account balance."""
def __init__(self, balance=0):
self.balance = balance
def __repr__(self):
return f"{type(self).__name__}(balance={self.balance})"
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
self.balance -= amount
class MinimumBalanceAccount(BankAccount):
"""Bank account which does not allow balance to drop below zero."""
def withdraw(self, amount):
if self.balance - amount < 0:
raise ValueError("Balance cannot be less than $0")
return super().withdraw(amount)
Alphabetic String
This is the AlphaString exercise in classes.py.
Make a custom string object AlphaString which only allows strings consisting entirely of alphabetic characters (A-Z).
Hint
Inherit from collections.UserString to implement this class.
Answers
from collections import UserString
class AlphaString(UserString):
"""Class that allows only alphabetic characters in the string."""
def __init__(self, string):
if not string.isalpha():
raise ValueError("AlphaString only allows alphabetic characters")
super().__init__(string)
Doubly-Linked Node
This is the DoublyLinkedNode exercise in classes.py.
The Node class represents a node with multiple child nodes attached.
Printing a node displays the “ancestry” of the node, starting with the root or base node, separated by / between the nodes, and ending with the node requested.
Example of creating a root note and child nodes:
>>> root = Node('A')
>>> child1 = root.make_child('1')
>>> grandchild1 = child1.make_child('a')
>>> grandchild2 = child1.make_child('b')
>>> child2 = root.make_child('2')
Examples of the string representations of these nodes:
>>> print(child1)
A / 1
>>> print(grandchild1)
A / 1 / a
>>> child1.name = '9'
>>> print(grandchild2)
A / 9 / b
>>> print(child2)
A / 2
>>> grandchild1.name
'a'
More examples:
>>> print(Node("Universe")
.make_child("Milky Way")
.make_child("Solar System")
.make_child("Earth")
... )
Universe / Milky Way / Solar System / Earth
>>> red_panda = (
... Node("Animalia")
... .make_child("Chordata")
... .make_child("Mammalia")
... .make_child("Carnivora")
... .make_child("Ailuridae")
... .make_child("Ailurus")
... .make_child("A. fulgens")
... )
>>> print(red_panda)
Animalia / Chordata / Mammalia / Carnivora / Ailuridae / Ailurus / A. fulgens
I’d like you to make a DoublyLinkedNode class that extends the Node class.
The method leaves returns all the children of the DoublyLinkedNode, or if there are no children, it returns a list containing only itself.
>>> from classes import DoublyLinkedNode
>>> root = DoublyLinkedNode('A')
>>> child1 = root.make_child('1')
>>> grandchild1 = child1.make_child('a')
>>> grandchild2 = child1.make_child('b')
>>> child2 = root.make_child('2')
>>> [node.name for node in root.leaves()]
['a', 'b', '2']
>>> [node.name for node in child1.leaves()]
['a', 'b']
>>> [node.name for node in child2.leaves()]
['2']
Answers
class Node:
"""Nodes for use in making hierarchies or trees."""
def __init__(self, name, *, ancestors=[]):
self.ancestors = list(ancestors)
self.name = name
def ancestors_and_self(self):
"""Return iterable with our ordered ancestors and our own node."""
return [*self.ancestors, self]
def make_child(self, *args, **kwargs):
"""Create and return a child node of the current node."""
return type(self)(*args, ancestors=self.ancestors_and_self(), **kwargs)
def __str__(self):
"""Return a slash-delimited ancestors hierarchy for this node."""
return " / ".join([
node.name
for node in [*self.ancestors, self]
])
def __repr__(self):
return self.name
class DoublyLinkedNode(Node):
"""Class with Nodes that are doubly-linked"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.children = []
def make_child(self, *args, **kwargs):
"""Create and return a child node of the current node."""
node = super().make_child(*args, **kwargs)
self.children.append(node)
return node
def leaves(self):
"""Return all leaves of the current node and all descendent nodes."""
if self.is_leaf():
return [self]
else:
return [
node
for child in self.children
for node in child.leaves()
]
def is_leaf(self):
"""Return True if this node is a leaf (node which has no children)."""
return not self.children
Last Updated Dictionary
This is the LastUpdatedDictionary exercise in classes.py.
Make a class LastUpdatedDictionary that maintains its items in last-updated order.
Normally dictionaries maintain their items in insertion order. When a key is inserted, it’s added to the end, but when a key is updated nothing is moved or rearranged.
Unlike a typical dictionary, LastUpdatedDictionary objects should move keys to the end of the dictionary whenever they’re updated.
Example:
>>> d = LastUpdatedDictionary({'a': 1, 'b': 2, 'c': 3})
>>> d
LastUpdatedDictionary([('a', 1), ('b', 2), ('c', 3)])
>>> d['b'] = 1
>>> d
LastUpdatedDictionary([('a', 1), ('c', 3), ('b', 1)])
Hint
Consider inheriting from the collections.OrderedDict class which maintains its values in insertion order.
Answers
from collections import OrderedDict
class LastUpdatedDictionary(OrderedDict):
def __setitem__(self, key, value):
self.pop(key, None)
super().__setitem__(key, value)
OrderedCounter
This is the OrderedCounter exercise in classes.py.
Make a class OrderedCounter that acts like collections.Counter except that it maintains its items in last-updated order.
You can think of this as a mashup between collections.Counter and LastUpdatedDictionary.
Example:
>>> d = OrderedCounter('hello there')
>>> d
OrderedCounter([('a', 1), ('b', 2), ('c', 3)])
>>> d['b'] = 1
>>> d
OrderedCounter([('a', 1), ('c', 3), ('b', 1)])
Hint
Consider inheriting from the collections.OrderedDict class which maintains its values in insertion order.
Answers
from collections import Counter, OrderedDict
class LastUpdatedDictionary(OrderedDict):
def __setitem__(self, key, value):
self.pop(key, None)
super().__setitem__(key, value)
class OrderedCounter(Counter, LastUpdatedDictionary):
"""Counter that maintains items in update order."""
def __repr__(self):
return f"{type(self).__name__}({LastUpdatedDictionary(self)})"