Extra Answers
File Exercises
Country Capitals CSV
This is the country_capitals.py exercise in the modules directory. Create the file country_capitals.py in the modules sub-directory of the exercises directory. To test it, run python test.py country_capitals.py from your exercises directory.
Download this country capitals file.
Write a program country_capitals.py that opens the file and extracts country name and capital city from each row, and write a new file to disk in the following format:
country,capital,population
China,Beijing,1330044000
India,New Delhi,1173108018
United States,Washington,310232863
The country rows should be sorted by largest population first.
Answers
import csv
import sys
from urllib.request import urlopen
def get_capital_data():
"""Return parsed CSV data from capitals API."""
url = "http://api.geonames.org/countryInfoCSV?username=truthfultechnology"
with urlopen(url) as response:
raw_data = response.read().decode('utf-8').splitlines()
reader = csv.DictReader(raw_data, delimiter="\t")
return list(reader)
def rearrange_capital_data(old_rows):
"""Sort CSV data by population and rename country column."""
def by_population(row): return int(row['population'])
new_rows = [{
'country': row['name'],
'capital': row['capital'],
'population': row['population'],
} for row in old_rows]
return sorted(new_rows, reverse=True, key=by_population)
def write_capital_file(file_name, csv_rows):
"""Write capital rows CSV file."""
headers = ["country", "capital", "population"]
with open(file_name, mode='wt') as out_file:
writer = csv.DictWriter(out_file, delimiter=",",
fieldnames=headers)
writer.writeheader()
writer.writerows(csv_rows)
def main(out_file_name):
original_data = get_capital_data()
new_data = rearrange_capital_data(original_data)
write_capital_file(out_file_name, new_data)
if __name__ == "__main__":
main(sys.argv[1])
GZip Fetch
This is the gzip_fetch.py exercise in the modules directory. Create the file gzip_fetch.py in the modules sub-directory of the exercises directory. To test it, run python test.py gzip_fetch.py from your exercises directory.
Write a program that downloads gzipped data from the Internet, extracts it, and saves it on disk all without using a temporary file.
You can use this gzipped response: https://httpbin.org/gzip
Example:
$ python gzip_fetch.py https://httpbin.org/gzip data.json
Answers
import gzip
from urllib.request import urlopen
import sys
def save_gzipped_data(url, filename):
with urlopen(url) as response:
with gzip.GzipFile(fileobj=response, mode='rb') as extracted:
with open(filename, mode='wb') as data_file:
data_file.write(extracted.read())
if __name__ == "__main__":
url, filename = sys.argv[1:]
save_gzipped_data(url, filename)
Decorator Exercises
JSONify
This is the jsonify exercise in decorators.py.
Make a decorator jsonify that will take the return value of the function and return a JSON-encoded version of the return value.
>>> @jsonify
... def get_thing():
... return {'trey': "red", 'diane': "purple"}
...
>>> get_thing()
'{"diane": "purple", "trey": "red"}'
Hint
You’ll want to use json.dumps
Answers
import json
from functools import wraps
def jsonify(func):
"""Return JSON-encoded value returned by decorated function."""
@wraps(func)
def new_func(*args, **kwargs):
return json.dumps(func(*args, **kwargs))
return new_func
Groot
This is the groot exercise in decorators.py.
Write a decorator that ignores the function it’s decorating and returns a function that prints “Groot”.
>>> @groot
... def greet(name):
... print(f"Hello {name}")
...
>>> greet("Trey")
Groot
Answers
from functools import wraps
def groot(func):
"""Return function which prints 'Groot' (ignore decoratee)."""
@wraps(func)
def new_func(*args, **kwargs):
print("Groot")
return new_func
Four
This is the four exercise in decorators.py.
Write a decorator that ignores the function it’s decorating and returns the number 4. In other words, it doesn’t return a new function like you might expect from a decorator.
>>> @four
... def greet(name):
... print(f"Hello {name}")
...
>>> greet
4
>>> type(greet)
<class 'int'>
Answers
def four(func):
"""Return 4 (ignore decorated function)."""
return 4
Hide Errors
This is the hide_errors exercise in decorators.py.
Make a decorator hide_errors that will suppress all non-system exiting exceptions raised by the target function, returning None if an exception was caught.
The decorator should act like this:
>>> @hide_errors
... def divide(x, y):
... return x / y
...
>>> divide(1, 0)
>>> divide(1, 2)
0.5
Answers
from functools import wraps
def hide_errors(func):
"""Trap and ignore non-exiting errors."""
@wraps(func)
def new_func(*args):
try:
return func(*args)
except Exception:
return
return new_func
Prompt on Error
This is the catch_all exercise in decorators.py.
Make a decorator catch_all that will catch all exceptions raised by a function, prompt the user to ask whether the program should be exited, and then act accordingly.
The decorator should act like this:
>>> @catch_all
... def divide(x, y):
... return x / y
...
>>> divide(1, 0)
Exception occurred: division by zero
Should we ignore this exception (Y/n)? y
>>> divide(1, 2)
0.5
>>> divide(1, 0)
Exception occurred: division by zero
Should we ignore this exception (Y/n)? n
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in decor
File "<stdin>", line 3, in divide
ZeroDivisionError: division by zero
Answers
from functools import wraps
def catch_all(func):
"""Trap non-exiting errors and ask user if we should ignore."""
@wraps(func)
def new_func(*args):
try:
return func(*args)
except Exception as error:
print(f"Exception occurred: {error}")
answer = input("Should we ignore this exception (Y/n)? ")
if answer.lower() == "n":
raise
return new_func
Make Partial
This is the make_partial exercise in decorators.py.
Make a decorator make_partial which makes a function return a new partially-evaluated function.
Example:
>>> @make_partial
... def add(x, y):
... return x + y
...
>>> add(1)
functools.partial(<function add at 0x7f4d086b4950>, 1)
>>> add(1)(2)
3
>>> add(1)(3)
4
Hint
Use functools.partial.
Answers
from functools import partial, wraps
def make_partial(func):
"""Returns a new partially-evaluated function."""
@wraps(func)
def new_func(*args):
return partial(func, *args)
return new_func
Count Calls
This is the count_calls exercise in decorators.py.
Make a decorator count_calls that keeps track of the number of times a function is called and passes the call count in as the first argument to the function.
Example usage:
>>> @count_calls
... def add(count, x, y):
... print(f"called {count} times")
... return x + y
...
>>> add(1, 2)
called 1 times
3
>>> add(1, 2)
called 2 times
3
>>> add(1, 3)
called 3 times
4
Tip
You may need to use nonlocal when making the inner function in your decorator.
Answers
from functools import wraps
def count_calls(func):
"""Pass call counts into first argument to the function."""
call_count = 0
@wraps(func)
def new_func(*args):
nonlocal call_count
call_count += 1
return func(call_count, *args)
return new_func
Object Exercises
Inverse Filter
This is the exclude exercise in objects.py.
Create an exclude function that only keeps items which fail a given predicate test. The function should accept a function and an iterable as its arguments and should return an iterable containing all items which yielded a falsey return value from the predicate function. This is basically the opposite of the built-in filter function.
>>> exclude(bool, [False, True, False])
[False, False]
>>> exclude(lambda x: len(x) > 3, ["red", "blue", "green"])
['red']
Answers
def exclude(condition, iterable):
"""Only keep items which fail a given predicate test"""
return [
var
for var in iterable
if not condition(var)
]
Call
This is the call exercise in objects.py.
Write a function call which calls a given function with any given positional and keyword arguments and returns the value returned by the function call.
>>> call(int)
0
>>> call(int, "5")
5
>>> call(len, "hello")
5
>>> list(call(zip, [1, 2], [3, 4]))
[(1, 3), (2, 4)]
Answers
def call(func, *args):
"""Call the function provided with the given arguments."""
return func(*args)
Call Later
This is the call_later exercises in objects.py.
Write a function call_later which accepts a function and a list of arguments and returns a new function that, when called, will call the function with the given arguments.
>>> names = []
>>> append_name = call_later(names.append, "Trey")
>>> append_name()
>>> names
['Trey']
>>> append_name()
>>> names
['Trey', 'Trey']
>>> call_zip = call_later(zip, [1, 2], [3, 4])
>>> list(call_zip())
[(1, 3), (2, 4)]
Answers
def call_later(func, *args):
"""Return a function to call given function with provided arguments."""
def new_func():
return func(*args)
return new_func
Call Again
This is the call_again exercise in objects.py.
Write a function call_again which accepts a function and a list of arguments and returns a tuple. The first item in the tuple should be the return value from calling the given function with the given arguments. The second item in the tuple should be a function that, when called, will call the function again with the given arguments.
>>> names = []
>>> response, names_as_str = call_again(str, names)
>>> response
'[]'
>>> names.append("Diane")
>>> names_as_str()
"['Diane']"
Answers
def call_again(func, *args):
"""Return function return value and a function to call again."""
def new_func():
return func(*args)
return func(*args), new_func
Only Once
This is the only_once exercise in objects.py.
Make a function only_once that accepts a function as an argument and returns a new function. The returned function should be identical to the original function except that when you try to call the function more than once, it shouldn’t let you.
>>> def do_something(x, y):
... print(f"doing something with {x} and {y}")
... return x * 2 + y ** 2
...
>>> do_something_once = only_once(do_something)
>>> do_something_once(1, 2)
doing something with 1 and 2
6
>>> do_something_once(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in new_func
ValueError: You can't call this function twice!
Answers
def only_once(func):
"""Return new version of the function that can only be called once."""
called_before = set()
def new_func(*args):
if args in called_before:
raise ValueError("You can't call this function twice!")
called_before.add(args)
return func(*args)
return new_func
Cache
This is the cache exercise in objects.py.
Write a cache function which takes a function as its argument and returns a function that is identical to the original function except that it caches its return values based on any positional arguments given.
>>> def do_something(x, y):
... print(f"doing something with {x} and {y}")
... return x * 2 + y ** 2
...
>>> do_something_cached = cache(do_something)
>>> do_something_cached(1, 2)
doing something with 1 and 2
6
>>> do_something_cached(1, 2)
6
Answers
def cache(func):
"""Return a cache-ing version of the given function."""
cached_calls = {}
def new_func(*args):
if args in cached_calls:
return cached_calls[args]
response = func(*args)
cached_calls[args] = response
return response
return new_func
Partial
This is the partial exercise in objects.py.
Make a partial function (like the one we already made) which accepts positional arguments and keyword arguments.
Answers
def partial(func, *first_args, **first_kwargs):
"""Partially evaluate the given function with the given arguments."""
def new_func(*args, **kwargs):
new_args = first_args + args
new_kwargs = first_kwargs.copy()
new_kwargs.update(kwargs)
return func(*new_args, **new_kwargs)
return new_func
Property Exercises
Point with Magnitude
This is the Point exercise in properties.py.
Make a Point class that has an auto-calculated magnitude attribute.
The magnitude should be calculated by sqrt(x**2 + y**2 + z**2).
Note
To test these changes, you should modify the PointTests class in properties_test.py to comment out the @unittest.skip for “magnitude property”.
Example:
>>> from properties import Point
>>> p = Point(2, 3, 6)
>>> p.magnitude
7.0
>>> p.y = 9
>>> p.magnitude
11.0
Answers
import math
class Point:
"""Three-dimensional point."""
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def __repr__(self):
"""Return dev-readable representation of Point."""
return f"Point({self.x}, {self.y}, {self.z})"
@property
def magnitude(self):
"""Return the magnitude of the Point"""
return math.sqrt(self.x**2 + self.y**2 + self.z**2)
Full Name
This is the Person exercise in properties.py.
Create the Person class so that it has a first_name and last_name attribute and a name property which is the combination first_name and last_name, with a space between.
>>> from properties import Person
>>> trey = Person("Trey", "Hunner")
>>> trey.name
'Trey Hunner'
>>> trey.last_name = "Smith"
>>> trey.name
'Trey Smith'
Answers
class Person:
"""Person with first and last name."""
def __init__(self, first, last):
self.first_name = first
self.last_name = last
@property
def name(self):
"""Return first and last name joined together."""
return " ".join((self.first_name, self.last_name))
Circle
This is the Circle exercise in properties.py.
Create the Circle class so that it:
can be constructed with an explicit
radiusor the default radius of1has an
areaproperty which auto-updates based on radius changeshas a
diameterproperty which auto-updates based on radius changesauto-updates the
radiusproperty based ondiameterchanges
Example usage:
>>> from properties import Circle
>>> circle1 = Circle()
>>> circle.radius
1
>>> circle.area
3.141592653589793
>>> circle.diameter
>>> circle.radius = 2
>>> circle.area
12.566370614359172
>>> circle.diameter
4
>>> circle.diameter = 10
>>> circle.radius
5.0
>>> circle.area
78.53981633974483
Answers
import math
class Circle:
"""Circle with radius, area, etc."""
def __init__(self, radius=1):
self.radius = radius
@property
def area(self):
return math.pi * self.radius ** 2
@property
def diameter(self):
return self.radius * 2
@diameter.setter
def diameter(self, diameter):
self.radius = diameter / 2.0
Log Radius Changes
This is the Circle exercise in properties.py.
Modify the Circle class we defined previously to use a property for the radius and record changes to the radius in a radius_changes attribute.
Note
To test these changes, you should modify the CircleTests class in properties_test.py to comment out the @unittest.skip for the lines from the appropriate test methods.
Hint
You will need to store the actual radius somewhere. You may want to use a _radius attribute when converting the radius to a property.
Example usage:
>>> from properties import Circle
>>> circle = Circle()
>>> circle.radius
1
>>> circle.radius_changes
[1]
>>> circle.radius = 2
>>> circle.radius = 3
>>> circle.radius_changes
[1, 2, 3]
Answers
import math
class Circle(object):
"""Circle with radius, area, etc."""
def __init__(self, radius=1):
self.radius_changes = []
self.radius = radius
@property
def area(self):
return math.pi * self.radius ** 2
@property
def diameter(self):
return self.radius * 2
@diameter.setter
def diameter(self, diameter):
self.radius = diameter / 2.0
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, radius):
self.radius_changes.append(radius)
self._radius = radius
Set Radius Error
This is the Circle exercise in properties.py.
Edit the Circle class and modify it again to raise a ValueError when the radius or diameter are set to a negative number.
Note
To test these changes, you should modify the CircleTests class in properties_test.py to comment out the @unittest.skip for the lines from the appropriate test methods.
Example usage:
>>> from properties import Circle
>>> circle = Circle()
>>> circle.radius = -10
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "shapes.py", line 18, in radius
raise ValueError("Radius cannot be negative")
ValueError: Radius cannot be negative
Answers
import math
class Circle(object):
"""Circle with radius, area, etc."""
def __init__(self, radius=1):
self.radius_changes = []
self.radius = radius
@property
def area(self):
return math.pi * self.radius ** 2
@property
def diameter(self):
return self.radius * 2
@diameter.setter
def diameter(self, diameter):
self.radius = diameter / 2.0
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, radius):
if radius < 0:
raise ValueError("Radius cannot be negative!")
self.radius_changes.append(radius)
self._radius = radius
Dunder Exercises
Pythonic Points
Edit the Point class in the properties.py file to:
Support using the
+operator between two pointsSupport using the
*operator between two a point and a numberSupport the
==and!=operators between two points
Note
To test these changes, you should modify the PointTests class in properties_test.py to comment out the @unittest.skip for “Pythonic Points”.
>>> p1 = Point(1, 2, 3)
>>> p2 = Point(4, 5, 6)
>>> p1 + p2
Point(5, 7, 9)
>>> p1 * 2
Point(2, 4, 6)
>>> p1 == p2
False
>>> p1 + Point(3, 3, 3) == p2
True
Answers
import math
class Point:
"""Three-dimensional point."""
def __init__(self, x, y, z):
self.x, self.y, self.z = x, y, z
def __repr__(self):
"""Return dev-readable representation of Point."""
return f"{self.__class__.__name__}({self.x}, {self.y}, {self.z})"
def __eq__(self, other):
"""Return True if our point is equal to the other point."""
return (self.x, self.y, self.z) == (other.x, other.y, other.z)
def __add__(self, other):
"""Return copy of our point, shifted by other."""
cls = self.__class__
return cls(self.x+other.x, self.y+other.y, self.z+other.z)
def __mul__(self, value):
"""Return new copy of our point, scaled by given value."""
cls = self.__class__
return cls(value*self.x, value*self.y, value*self.z)
def __rmul__(self, value):
"""Return new copy of our point, scaled by given value."""
return self.__mul__(value)
@property
def magnitude(self):
"""Return the magnitude of the Point"""
return math.sqrt(self.x**2 + self.y**2 + self.z**2)
Truthy Account
Edit the BankAccount class in the classes.py file to make account objects truthy when the bank account has money in it.
Note
To test these changes, you should modify the BankAccountTests class in classes_test.py to comment out the lines with “Comment this line for truthy accounts exercise.”
Example usage:
>>> from classes import BankAccount
>>> trey_account = BankAccount(100)
>>> bool(trey_account)
True
>>> trey_account.withdraw(100)
>>> bool(trey_account)
False
>>> trey_account.withdraw(1)
>>> bool(trey_account)
False
You can use this class:
class BankAccount:
"""Bank account that supports truthiness."""
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)
Hint
Use the __bool__ method
Answers
class BankAccount:
"""Bank account that supports truthiness."""
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)
def __bool__(self):
return self.balance > 0
Comparable Account
Edit the BankAccount class in the classes.py file to allow accounts to be compared using arithmetic comparison operators.
Note
To test these changes, you should modify the BankAccountTests class in classes_test.py to comment out the line with “Comment this line for BankAccount comparison exercise.”
Example:
>>> from classes import BankAccount
>>> account1 = BankAccount(balance=100)
>>> account2 = BankAccount(balance=200)
>>> account1 == account2
False
>>> account1 < account2
True
>>> account1 >= account2
False
Answers
class BankAccount:
"""Bank account that supports truthiness."""
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)
def __eq__(self, rhs):
return self.balance == rhs.balance
def __lt__(self, rhs):
return self.balance < rhs.balance
def __gt__(self, rhs):
return self.balance > rhs.balance
def __le__(self, rhs):
return self.balance <= rhs.balance
def __ge__(self, rhs):
return self.balance >= rhs.balance
Caseless String
This is the CaselessString exercise in dunder.py.
Use UserString to make a CaselessString class which does comparisons in a case insensitive manner.
Example:
>>> from dunder import CaselessString
>>> s1 = CaselessString("Hello there")
>>> s2 = CaselessString("hello there")
>>> s3 = CaselessString("HELLO THERE")
>>> s1 == s2 == s3
True
>>> s1.lower() == s2.upper()
True
Answers
We are using the decorator functools.total_ordering. It allows you to define __eq__ or __ne__ and one of the other comparison operators __lt__ or __gt__. It takes care of all the rest!
from collections import UserString
from functools import total_ordering
@total_ordering
class CaselessString(UserString):
"""Class whose strings are compared caselessly."""
def __eq__(self, other):
return self.data.lower() == other.lower()
def __lt__(self, other):
return self.data.lower() < other.lower()
Easy Dict
Edit the EasyDict class in the dunder.py file to make an EasyDict class that can be used with both attribute and item syntax.
Run python test.py EasyDict in your exercises directory.
Example:
>>> from dunder import EasyDict
>>> a = EasyDict()
>>> a['shoe'] = "blue"
>>> a.shoe
"blue"
>>> a['shoe']
"blue"
>>> a.car = "green"
>>> a['car']
"green"
Hint
Use the __getitem__ and __setitem__ methods
Answers
class EasyDict:
"""Class which allows both attribute and get/set item syntax."""
def __getitem__(self, key):
return getattr(self, key)
def __setitem__(self, key, value):
setattr(self, key, value)
Cyclic List
This is the CyclicList exercise in dunder.py.
Create a list-like data structure that loops in a cyclic manner:
>>> from dunder import CyclicList
>>> numbers = CyclicList([1, 2, 3, 4])
>>> numbers[1]
2
>>> numbers[2]
3
>>> numbers[12]
1
>>> [x for x, _ in zip(numbers, range(10))]
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2]
>>> numbers[35]
4
Answers
from collections import UserList
class CyclicList(UserList):
"""Class with list-like structure that loops cyclicly."""
def __getitem__(self, i):
return super().__getitem__(i % len(self))
def __setitem__(self, i, v):
return super().__setitem__(i % len(self), v)