Exercise Answers¶
The code cells below are example answers to the workshop exercises. They are useful if you get stuck and need a hint or if you want to use them as a comparison with your own attempts
1.1) Use unpacking for pretty printing¶
In [ ]:
Copied!
counties = ["Anoka", "Dakota", "Carver", "Hennepin", "Ramsey", "Scott", "Washington"]
print(*counties, sep='\n')
counties = ["Anoka", "Dakota", "Carver", "Hennepin", "Ramsey", "Scott", "Washington"]
print(*counties, sep='\n')
Anoka Dakota Carver Hennepin Ramsey Scott Washington
1.2) Use standard library data classes¶
In [ ]:
Copied!
from dataclasses import dataclass
@dataclass
class Record:
total_population: int
population_in_poverty: int
record = Record(5000, 200)
record.total_population = 6000
print(record)
from dataclasses import dataclass
@dataclass
class Record:
total_population: int
population_in_poverty: int
record = Record(5000, 200)
record.total_population = 6000
print(record)
Record(total_population=6000, population_in_poverty=200)
1.3) Use the built-in min and max functions¶
In [ ]:
Copied!
from random import randint
nums = [randint(-1000, 1000) for i in range(20)]
print(max(nums), min(nums))
from random import randint
nums = [randint(-1000, 1000) for i in range(20)]
print(max(nums), min(nums))
891 -899
1.4) Just do things¶
In [ ]:
Copied!
from typing import NamedTuple
class Record(NamedTuple):
total_population: int
population_in_poverty: int
record1 = Record(5000, 2000)
record2 = Record(200, 10)
record3 = Record("400", "30")
def poverty_rate(record):
total_pop, pop_in_poverty = record
return int(pop_in_poverty) / int(total_pop)
for record in (record1, record2, record3):
print(poverty_rate(record))
from typing import NamedTuple
class Record(NamedTuple):
total_population: int
population_in_poverty: int
record1 = Record(5000, 2000)
record2 = Record(200, 10)
record3 = Record("400", "30")
def poverty_rate(record):
total_pop, pop_in_poverty = record
return int(pop_in_poverty) / int(total_pop)
for record in (record1, record2, record3):
print(poverty_rate(record))
0.4 0.05 0.075
1.5) Use Pythonic patterns for setup and teardown boilerplate¶
In [3]:
Copied!
with open("data.csv", "w") as f:
f.write("Important data")
raise ValueError
with open("data.csv", "w") as f:
f.write("Important data")
raise ValueError
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Cell In[3], line 3 1 with open("data.csv", "w") as f: 2 f.write("Important data") ----> 3 raise ValueError ValueError:
2.1) Use type hints¶
In [ ]:
Copied!
from itertools import cycle
from collections.abc import Iterable
from typing import NamedTuple
class Vertex(NamedTuple):
x: float
y: float
def calculate_area(vertices: Iterable[Vertex]) -> float:
subtotals = []
vertex_cycle = cycle(vertices)
next(vertex_cycle)
for vertex in vertices:
next_vertex = next(vertex_cycle)
subtotal = vertex.x * next_vertex.y - vertex.y * next_vertex.x
subtotals.append(subtotal)
area = abs(sum(subtotals) / 2)
return area
vertices = (Vertex(4, 10), Vertex(9, 7), Vertex(11, 2), Vertex(2, 2))
calculate_area(vertices)
from itertools import cycle
from collections.abc import Iterable
from typing import NamedTuple
class Vertex(NamedTuple):
x: float
y: float
def calculate_area(vertices: Iterable[Vertex]) -> float:
subtotals = []
vertex_cycle = cycle(vertices)
next(vertex_cycle)
for vertex in vertices:
next_vertex = next(vertex_cycle)
subtotal = vertex.x * next_vertex.y - vertex.y * next_vertex.x
subtotals.append(subtotal)
area = abs(sum(subtotals) / 2)
return area
vertices = (Vertex(4, 10), Vertex(9, 7), Vertex(11, 2), Vertex(2, 2))
calculate_area(vertices)
2.2) Add a docstring to a function¶
In [ ]:
Copied!
def calculate_area(vertices: Iterable[Vertex]) -> float:
"""
Calculate the area of a polygon given the coordinates of its vertices
Args:
vertices (Iterable[Vertex]):
An iterable, such as a list or tuple, of Vertex objects
holding the (x, y) coordinates of each vertex
Returns:
float: The area of the polygon
"""
subtotals = []
vertex_cycle = cycle(vertices)
next(vertex_cycle)
for vertex in vertices:
next_vertex = next(vertex_cycle)
subtotal = vertex.x * next_vertex.y - vertex.y * next_vertex.x
subtotals.append(subtotal)
area = abs(sum(subtotals) / 2)
return area
vertices = (Vertex(4, 10), Vertex(9, 7), Vertex(11, 2), Vertex(2, 2))
calculate_area(vertices)
def calculate_area(vertices: Iterable[Vertex]) -> float:
"""
Calculate the area of a polygon given the coordinates of its vertices
Args:
vertices (Iterable[Vertex]):
An iterable, such as a list or tuple, of Vertex objects
holding the (x, y) coordinates of each vertex
Returns:
float: The area of the polygon
"""
subtotals = []
vertex_cycle = cycle(vertices)
next(vertex_cycle)
for vertex in vertices:
next_vertex = next(vertex_cycle)
subtotal = vertex.x * next_vertex.y - vertex.y * next_vertex.x
subtotals.append(subtotal)
area = abs(sum(subtotals) / 2)
return area
vertices = (Vertex(4, 10), Vertex(9, 7), Vertex(11, 2), Vertex(2, 2))
calculate_area(vertices)
Out[ ]:
45.5
3.1) Use the right data structure for immutable sequences¶
In [ ]:
Copied!
import sys
def tuple_from_range(start, end):
"""Create a tuple from a range of values"""
return tuple(range(start, end + 1))
start = 1900
end = 2030
studyYears = tuple_from_range(start, end)
print(studyYears)
print("Bytes used: ", sys.getsizeof(studyYears))
import sys
def tuple_from_range(start, end):
"""Create a tuple from a range of values"""
return tuple(range(start, end + 1))
start = 1900
end = 2030
studyYears = tuple_from_range(start, end)
print(studyYears)
print("Bytes used: ", sys.getsizeof(studyYears))
(1900, 1901, 1902, 1903, 1904, 1905, 1906, 1907, 1908, 1909, 1910, 1911, 1912, 1913, 1914, 1915, 1916, 1917, 1918, 1919, 1920, 1921, 1922, 1923, 1924, 1925, 1926, 1927, 1928, 1929, 1930, 1931, 1932, 1933, 1934, 1935, 1936, 1937, 1938, 1939, 1940, 1941, 1942, 1943, 1944, 1945, 1946, 1947, 1948, 1949, 1950, 1951, 1952, 1953, 1954, 1955, 1956, 1957, 1958, 1959, 1960, 1961, 1962, 1963, 1964, 1965, 1966, 1967, 1968, 1969, 1970, 1971, 1972, 1973, 1974, 1975, 1976, 1977, 1978, 1979, 1980, 1981, 1982, 1983, 1984, 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030) Bytes used: 1088
3.2) Use the right data structure for membership lookup¶
In [ ]:
Copied!
placeNames_list = ["Kinshasa", "Duluth", "Uruguay"] * 1_000_000
placeNames_set = set(placeNames_list)
# O(1) set look-up
if "Dinkytown" not in placeNames_set:
print("Missing.")
placeNames_list = ["Kinshasa", "Duluth", "Uruguay"] * 1_000_000
placeNames_set = set(placeNames_list)
# O(1) set look-up
if "Dinkytown" not in placeNames_set:
print("Missing.")
3.3) Use generators¶
In [1]:
Copied!
from itertools import cycle
from random import randint
class Random_Vertex:
def __init__(self):
self.x = randint(0, 100)
self.y = randint(0, 100)
def generate_polygon_vertices(num_polygons, num_sides):
for _ in range(num_polygons):
vertices = (Random_Vertex() for _ in range(num_sides))
yield vertices
def calculate_area(vertices):
subtotals = []
vertex_cycle = cycle(vertices)
next(vertex_cycle)
for vertex in vertices:
next_vertex = next(vertex_cycle)
subtotal = vertex.x * next_vertex.y - vertex.y * next_vertex.x
subtotals.append(subtotal)
area = abs(sum(subtotals) / 2)
return area
from itertools import cycle
from random import randint
class Random_Vertex:
def __init__(self):
self.x = randint(0, 100)
self.y = randint(0, 100)
def generate_polygon_vertices(num_polygons, num_sides):
for _ in range(num_polygons):
vertices = (Random_Vertex() for _ in range(num_sides))
yield vertices
def calculate_area(vertices):
subtotals = []
vertex_cycle = cycle(vertices)
next(vertex_cycle)
for vertex in vertices:
next_vertex = next(vertex_cycle)
subtotal = vertex.x * next_vertex.y - vertex.y * next_vertex.x
subtotals.append(subtotal)
area = abs(sum(subtotals) / 2)
return area
Easier: Use a generator expression to find the triangle with the maximum area instead of a list comprehension
In [5]:
Copied!
triangles = generate_polygon_vertices(1_000_000, 3)
max([calculate_area(triangle) for triangle in triangles])
triangles = generate_polygon_vertices(1_000_000, 3)
max([calculate_area(triangle) for triangle in triangles])
Out[5]:
5000.0
Harder: Write a generator to replace the list comprehension instead of using a generator expression.
In [6]:
Copied!
def calculate_areas(polygons):
for polygon in polygons:
yield(calculate_area(polygon))
triangles = generate_polygon_vertices(1_000_000, 3)
max(calculate_areas(triangles))
def calculate_areas(polygons):
for polygon in polygons:
yield(calculate_area(polygon))
triangles = generate_polygon_vertices(1_000_000, 3)
max(calculate_areas(triangles))
Out[6]:
5000.0
3.4) Compare memory use of lists vs. generators¶
In [2]:
Copied!
import tracemalloc
tracemalloc.start()
triangles = generate_polygon_vertices(1_000_000, 3)
max([calculate_area(triangle) for triangle in triangles])
current, peak = tracemalloc.get_traced_memory()
print(peak)
import tracemalloc
tracemalloc.start()
triangles = generate_polygon_vertices(1_000_000, 3)
max([calculate_area(triangle) for triangle in triangles])
current, peak = tracemalloc.get_traced_memory()
print(peak)
32452069
In [2]:
Copied!
import tracemalloc
tracemalloc.start()
triangles = generate_polygon_vertices(1_000_000, 3)
max(calculate_area(triangle) for triangle in triangles)
current, peak = tracemalloc.get_traced_memory()
print(peak)
import tracemalloc
tracemalloc.start()
triangles = generate_polygon_vertices(1_000_000, 3)
max(calculate_area(triangle) for triangle in triangles)
current, peak = tracemalloc.get_traced_memory()
print(peak)
31073
3.5) Check execution speed of lists vs. generators¶
In [8]:
Copied!
%%timeit
triangles = generate_polygon_vertices(1_000, 3)
max([calculate_area(triangle) for triangle in triangles])
%%timeit
triangles = generate_polygon_vertices(1_000, 3)
max([calculate_area(triangle) for triangle in triangles])
7.55 ms ± 40.6 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [9]:
Copied!
%%timeit
triangles = generate_polygon_vertices(1_000, 3)
max(calculate_area(triangle) for triangle in triangles)
%%timeit
triangles = generate_polygon_vertices(1_000, 3)
max(calculate_area(triangle) for triangle in triangles)
7.54 ms ± 32.5 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)