"**Note**: Click on \"*Kernel*\" > \"*Restart Kernel and Run All*\" in [JupyterLab](https://jupyterlab.readthedocs.io/en/stable/) *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 <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_mb.png\">](https://mybinder.org/v2/gh/webartifex/intro-to-python/develop?urlpath=lab/tree/08_mfr/03_exercises.ipynb)."
"The exercises below assume that you have read the [first <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/08_mfr/00_content.ipynb) and [second <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/08_mfr/01_content.ipynb) part of Chapter 8.\n",
"\n",
"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."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Packing & Unpacking with Functions (continued)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q1**: Copy your solution to **Q10** from the \"*Packing & Unpacking with Functions*\" exercise in [Chapter 7 <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/07_sequences/04_exercises.ipynb) into the code cell below!"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import collections.abc as abc"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def product(*args, ...):\n",
" \"\"\"Multiply all arguments.\"\"\"\n",
" ...\n",
" ...\n",
" ...\n",
" ...\n",
"\n",
" ...\n",
" ...\n",
"\n",
" return ..."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q2**: Verify that all test cases below work (i.e., the `assert` statements must *not* raise an `AssertionError`)!"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"assert product(42) == 42"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"assert product(2, 5, 10) == 100"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"assert product(2, 5, 10, start=2) == 200"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"one_hundred = [2, 5, 10]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"assert product(one_hundred) == 100"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"assert product(*one_hundred) == 100"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q3**: Verify that `product()` raises a `TypeError` when called without any arguments!"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This implementation of `product()` is convenient to use, in particular, because we can pass it any *collection* object with or without *unpacking* it.\n",
"\n",
"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.\n",
"\n",
"**Q4**: Click through the following code cells and observe what they do!\n",
"\n",
"The [*stream.py* <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_gh.png\">](https://github.com/webartifex/intro-to-python/blob/develop/08_mfr/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."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from stream import make_finite_stream"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"data = make_finite_stream()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"data"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"type(data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`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), ..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"next(data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"... it has *no* concept of a \"length:\" The built-in [len() <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_py.png\">](https://docs.python.org/3/library/functions.html#len) function raises a `TypeError`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"len(data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can use the built-in [list() <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_py.png\">](https://docs.python.org/3/library/functions.html#func-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."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"list(data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To be more realistic, `make_finite_stream()` creates `generator` objects producing a varying number of elements."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"list(make_finite_stream())"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"list(make_finite_stream())"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"list(make_finite_stream())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"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()`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product(make_finite_stream())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**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!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" < your answer >"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q6**: Adapt `product()` one last time to make it work with `generator` objects, or more generallz *iterators*, as well!\n",
"\n",
"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() <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_py.png\">](https://docs.python.org/3/library/functions.html#max), [min() <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_py.png\">](https://docs.python.org/3/library/functions.html#min), and [sum() <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_py.png\">](https://docs.python.org/3/library/functions.html#sum) functions: What kind of argument do they take?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def product(*args, ...):\n",
" \"\"\"Multiply all arguments.\"\"\"\n",
" ...\n",
" ...\n",
" ...\n",
" ...\n",
"\n",
" ...\n",
" ...\n",
"\n",
" return ..."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The final version of `product()` behaves like built-ins in edge cases (i.e., `sum()` also raises a `TypeError` when called without arguments), ..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"... works with the arguments passed either separately as *positional* arguments, *packed* together into a single *collection* argument, or *unpacked*, ..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product(42)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product(2, 5, 10)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product([2, 5, 10])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product(*[2, 5, 10])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"... and can handle *streaming* data with *indefinite* \"length.\""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product(make_finite_stream())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"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.\n",
"\n",
"Yet, two lessons are important to take away:\n",
"- It is a good idea to *mimic* the behavior of *built-ins* when accepting arguments, and\n",
"- make functions capable of working with *streaming* data."