Note: Click on "Kernel" > "Restart Kernel and Run All" in JupyterLab after finishing the exercises to ensure that your solution runs top to bottom without any errors. If you cannot run this file on your machine, you may want to open it in the cloud .
The exercises below assume that you have read the first and second
part of Chapter 8.
The ...
's in the code cells indicate where you need to fill in code snippets. The number of ...
's within a code cell give you a rough idea of how many lines of code are needed to solve the task. You should not need to create any additional code cells for your final solution. However, you may want to use temporary code cells to try out some ideas.
Q1: Copy your solution to Q10 from the "Packing & Unpacking with Functions" exercise in Chapter 7 into the code cell below!
import collections.abc as abc
def product(*args, ...):
"""Multiply all arguments."""
...
...
...
...
...
...
return ...
Q2: Verify that all test cases below work (i.e., the assert
statements must not raise an AssertionError
)!
assert product(42) == 42
assert product(2, 5, 10) == 100
assert product(2, 5, 10, start=2) == 200
one_hundred = [2, 5, 10]
assert product(one_hundred) == 100
assert product(*one_hundred) == 100
Q3: Verify that product()
raises a TypeError
when called without any arguments!
product()
This implementation of product()
is convenient to use, in particular, because we can pass it any collection object with or without unpacking it.
However, product()
suffers from one last flaw: We cannot pass it a stream of data, as modeled, for example, with a generator
object that produces elements on a one-by-one basis.
Q4: Click through the following code cells and observe what they do!
The stream.py module in the book's repository provides a
make_finite_stream()
function. It is a factory function creating objects of type generator
that we use to model streaming data.
from stream import make_finite_stream
data = make_finite_stream()
data
type(data)
generator
objects are good for only one thing: Giving us the "next" element in a series of possibly infinitely many objects. While the data
object is finite (i.e., execute the next code cell until you see a StopIteration
exception), ...
next(data)
... it has no concept of a "length:" The built-in len() function raises a
TypeError
.
len(data)
We can use the built-in list() constructor to materialize all elements. However, in a real-world scenario, these may not fit into our machine's memory! If you get an empty
list
object below, you have to create a new data
object above again.
list(data)
To be more realistic, make_finite_stream()
creates generator
objects producing a varying number of elements.
list(make_finite_stream())
list(make_finite_stream())
list(make_finite_stream())
Let's see what happens if we pass a generator
object, as created by make_finite_stream()
, instead of a materialized collection, like one_hundred
, to product()
.
product(make_finite_stream())
Q5: What line causes the TypeError
? What line is really the problem in product()
? Hint: These may be different lines. Describe what happens on each line in the function's body until the exception is raised!
< your answer >
Q6: Adapt product()
one last time to make it work with generator
objects, or more generallz iterators, as well!
Hints: This task is as easy as replacing Collection
with something else. Which of the three behaviors of collections do generator
objects also exhibit? You may want to look at the documentations on the built-in max() , min()
, and sum()
functions: What kind of argument do they take?
def product(*args, ...):
"""Multiply all arguments."""
...
...
...
...
...
...
return ...
The final version of product()
behaves like built-ins in edge cases (i.e., sum()
also raises a TypeError
when called without arguments), ...
product()
... works with the arguments passed either separately as positional arguments, packed together into a single collection argument, or unpacked, ...
product(42)
product(2, 5, 10)
product([2, 5, 10])
product(*[2, 5, 10])
... and can handle streaming data with indefinite "length."
product(make_finite_stream())
In real-world projects, the data science practitioner must decide if it is worthwhile to make a function usable in various different forms as we do in this exercise. This may be over-engineered.
Yet, two lessons are important to take away: