Merge in "07-sequences"

Merge branch "07-sequences" into "develop"

Summary of the merged in commits:
 * 8c9af64: Add review and exercises for notebook 07
 * 3cab7a3: Add note why to use python -m pip ...
 * 8fd14a6: Streamline previous content
 * 9743622: Add initial version of notebook 07
 * 00e3755: Adjust content overview in README.md
 * 04d5395: Streamline previous content
This commit is contained in:
Alexander Hess 2019-11-06 16:21:19 +01:00
commit 5df00ebd48
14 changed files with 12568 additions and 740 deletions

View file

@ -792,13 +792,12 @@
"**Part 2: Managing Data and Memory**\n",
"\n",
"- How is data stored in memory?\n",
" 5. [Numbers](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers.ipynb)\n",
" 6. Text\n",
" 7. Sequences\n",
" 5. [Bits & Numbers](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers.ipynb)\n",
" 6. [Bytes & Text](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/06_text.ipynb)\n",
" 7. [Sequential Data](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb)\n",
" 8. Mappings & Sets\n",
" 9. Arrays\n",
"- How can we create custom data types?\n",
" 10. Object-Orientation"
" 9. Object-Orientation"
]
},
{

File diff suppressed because it is too large Load diff

View file

@ -19,7 +19,7 @@
}
},
"source": [
"In [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb), we typed the **business logic** of our little program to calculate the mean of a subset of a list of numbers right into the code cells. Then, we executed them one after another. We had no way of **re-using** the code except for either re-executing the cells or copying and pasting their contents into other cells. And whenever we find ourselves doing repetitive manual work, we can be sure that there must be a way of automating what we are doing.\n",
"In [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Example:-Averaging-Even-Numbers), we typed the code to calculate the average of the even numbers in a list into several code cells. Then, we executed them one after another. We had no way of **re-using** the code except for either re-executing the cells or copying and pasting their contents into other cells. And, whenever we find ourselves doing repetitive manual work, we can be sure that there must be a way of automating what we are doing.\n",
"\n",
"At the same time, we executed built-in functions (e.g., [print()](https://docs.python.org/3/library/functions.html#print), [sum()](https://docs.python.org/3/library/functions.html#sum), [len()](https://docs.python.org/3/library/functions.html#len), [id()](https://docs.python.org/3/library/functions.html#id), or [type()](https://docs.python.org/3/library/functions.html#type)) that obviously must be re-using the same parts inside core Python every time we use them.\n",
"\n",
@ -45,7 +45,7 @@
}
},
"source": [
"So-called **[user-defined functions](https://docs.python.org/3/reference/compound_stmts.html#function-definitions)** may be created with the `def` statement. To extend an already familiar example, we re-use the introductory example from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) in its final Pythonic version and transform it into the function `average_evens()` below. \n",
"So-called **[user-defined functions](https://docs.python.org/3/reference/compound_stmts.html#function-definitions)** may be created with the `def` statement. To extend an already familiar example, we re-use the introductory example from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Best-Practices) in its final Pythonic version and transform it into the function `average_evens()` below. \n",
"\n",
"A function's **name** must be chosen according to the same naming rules as ordinary variables as Python manages function names like variables. In this book, we further adopt the convention of ending function names with parentheses `()` in text cells for faster comprehension when reading (i.e., `average_evens()` vs. `average_evens`). These are *not* part of the name but must always be written out in the `def` statement for syntactic reasons.\n",
"\n",
@ -55,9 +55,9 @@
"\n",
"Together, the name and the list of parameters are also referred to as the function's **[signature](https://en.wikipedia.org/wiki/Type_signature)** (i.e., `average_evens(numbers)` below).\n",
"\n",
"A function may come with an *explicit* **[return value](https://docs.python.org/3/reference/simple_stmts.html#the-return-statement)** (i.e., \"result\" or \"output\") specified with the `return` statement: Functions that have one are considered **fruitful**; otherwise, they are **void**. Functions of the latter kind are still useful because of their **side effects** (e.g., the [print()](https://docs.python.org/3/library/functions.html#print) built-in). Strictly speaking, they also have an *implicit* return value of `None` that is different from the `False` we saw in Chapter 1.\n",
"A function may come with an *explicit* **return value** (i.e., \"result\" or \"output\") specified with the `return` statement (cf., [reference](https://docs.python.org/3/reference/simple_stmts.html#the-return-statement)): Functions that have one are considered **fruitful**; otherwise, they are **void**. Functions of the latter kind are still useful because of their **side effects** as, for example, the built-in [print()](https://docs.python.org/3/library/functions.html#print) function. Strictly speaking, they also have an *implicit* return value of `None`.\n",
"\n",
"A function should define a **docstring** that describes what it does in a short subject line, what parameters it expects (i.e., their types), and what it returns (if anything). A docstring is a syntactically valid multi-line string (i.e., type `str`) defined within **triple-double quotes** `\"\"\"`. Strings are covered in depth in [Chapter 6](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/06_text.ipynb). Widely adopted standards as to how to format a docstring are [PEP 257](https://www.python.org/dev/peps/pep-0257/) and section 3.8 in [Google's Python Style Guide](https://github.com/google/styleguide/blob/gh-pages/pyguide.md)."
"A function should define a **docstring** that describes what it does in a short subject line, what parameters it expects (i.e., their types), and what it returns (if anything). A docstring is a syntactically valid multi-line string (i.e., type `str`) defined within **triple-double quotes** `\"\"\"`. Strings are covered in depth in [Chapter 6](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/06_text.ipynb#The-str-Type). Widely adopted standards as to how to format a docstring are [PEP 257](https://www.python.org/dev/peps/pep-0257/) and section 3.8 in [Google's Python Style Guide](https://github.com/google/styleguide/blob/gh-pages/pyguide.md)."
]
},
{
@ -142,7 +142,7 @@
{
"data": {
"text/plain": [
"140519730762208"
"139646582981088"
]
},
"execution_count": 3,
@ -302,7 +302,7 @@
},
"outputs": [],
"source": [
"nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
"nums = [7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]"
]
},
{
@ -317,7 +317,7 @@
{
"data": {
"text/plain": [
"6.0"
"7.0"
]
},
"execution_count": 9,
@ -337,7 +337,7 @@
}
},
"source": [
"The return value is commonly assigned to a new variable for later reference. Otherwise, we would lose access to it in memory right away."
"The return value is commonly assigned to a variable for later reference. Otherwise, we would lose access to it in memory right away."
]
},
{
@ -365,7 +365,7 @@
{
"data": {
"text/plain": [
"6.0"
"7.0"
]
},
"execution_count": 11,
@ -493,7 +493,7 @@
}
},
"source": [
"[PythonTutor](http://pythontutor.com/visualize.html#code=nums%20%3D%20%5B1,%202,%203,%204,%205,%206,%207,%208,%209,%2010%5D%0A%0Adef%20average_evens%28numbers%29%3A%0A%20%20%20%20evens%20%3D%20%5Bn%20for%20n%20in%20numbers%20if%20n%20%25%202%20%3D%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28evens%29%20/%20len%28evens%29%0A%20%20%20%20return%20average%0A%0Arv%20%3D%20average_evens%28nums%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) visualizes what happens in memory: To be precise, in the exact moment when the function call is initiated and `nums` passed in as the `numbers` argument, there are *two* pointers to the *same* `list` object (cf., steps 4-5 in the visualization). We also see how Python creates a *new* **frame** that holds the function's local scope (i.e., \"internal names\") in addition to the **global** frame. Frames are nothing but [namespaces](https://en.wikipedia.org/wiki/Namespace) to *isolate* the names of different **scopes** from each other. The list comprehension `[n for n in numbers if n % 2 == 0]` constitutes yet another frame that is in scope as the `list` object assigned to `evens` is *being* created (cf., steps 6-18). When the function returns, only the global frame is left (cf., steps 21-22)."
"[PythonTutor](http://pythontutor.com/visualize.html#code=nums%20%3D%20%5B1,%202,%203,%204,%205,%206,%207,%208,%209,%2010,%2011,%2012%5D%0A%0Adef%20average_evens%28numbers%29%3A%0A%20%20%20%20evens%20%3D%20%5Bn%20for%20n%20in%20numbers%20if%20n%20%25%202%20%3D%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28evens%29%20/%20len%28evens%29%0A%20%20%20%20return%20average%0A%0Arv%20%3D%20average_evens%28nums%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) visualizes what happens in memory: To be precise, in the exact moment when the function call is initiated and `nums` passed in as the `numbers` argument, there are *two* pointers to the *same* `list` object (cf., steps 4-5 in the visualization). We also see how Python creates a *new* **frame** that holds the function's local scope (i.e., \"internal names\") in addition to the **global** frame. Frames are nothing but [namespaces](https://en.wikipedia.org/wiki/Namespace) to *isolate* the names of different **scopes** from each other. The list comprehension `[n for n in numbers if n % 2 == 0]` constitutes yet another frame that is in scope as the `list` object assigned to `evens` is *being* created (cf., steps 6-20). When the function returns, only the global frame is left (cf., last step)."
]
},
{
@ -565,7 +565,7 @@
{
"data": {
"text/plain": [
"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
"[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]"
]
},
"execution_count": 16,
@ -600,7 +600,7 @@
{
"data": {
"text/plain": [
"6.0"
"7.0"
]
},
"execution_count": 17,
@ -635,7 +635,7 @@
{
"data": {
"text/plain": [
"6.0"
"7.0"
]
},
"execution_count": 18,
@ -655,9 +655,9 @@
}
},
"source": [
"[PythonTutor](http://pythontutor.com/visualize.html#code=nums%20%3D%20%5B1,%202,%203,%204,%205,%206,%207,%208,%209,%2010%5D%0A%0Adef%20average_wrong%28numbers%29%3A%0A%20%20%20%20evens%20%3D%20%5Bn%20for%20n%20in%20nums%20if%20n%20%25%202%20%3D%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28evens%29%20/%20len%28evens%29%0A%20%20%20%20return%20average%0A%0Arv%20%3D%20average_wrong%28%5B123,%20456,%20789%5D%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) is again helpful at visualizing the error interactively: Creating the `list` object `evens` eventually points to takes *12* computational steps, namely one for setting up an empty `list` object, *ten* for filling it with elements derived from `nums` in the global scope, and one to make `evens` point at it (cf., steps 6-18).\n",
"[PythonTutor](http://pythontutor.com/visualize.html#code=nums%20%3D%20%5B1,%202,%203,%204,%205,%206,%207,%208,%209,%2010,%2011,%2012%5D%0A%0Adef%20average_wrong%28numbers%29%3A%0A%20%20%20%20evens%20%3D%20%5Bn%20for%20n%20in%20nums%20if%20n%20%25%202%20%3D%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28evens%29%20/%20len%28evens%29%0A%20%20%20%20return%20average%0A%0Arv%20%3D%20average_wrong%28%5B123,%20456,%20789%5D%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) is again helpful at visualizing the error interactively: Creating the `list` object `evens` eventually points to takes *16* computational steps, namely two for managing the list comprehension, one for setting up an empty `list` object, *twelve* for filling it with elements derived from `nums` in the global scope (i.e., that is the error), and one to make `evens` point at it (cf., steps 6-21).\n",
"\n",
"The frames logic shown by PythonTutor is the mechanism by which Python not only manages the names inside *one* function call but also for *many* potentially simultaneous* calls, as revealed in [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb). It is the reason why we may re-use the same names for the parameters and variables inside both `average_evens()` and `average_wrong()` without Python mixing them up. So, as we already read in the [Zen of Python](https://www.python.org/dev/peps/pep-0020/), \"namespaces are one honking great idea\" (cf., `import this`), and a frame is just a special kind of namespace."
"The frames logic shown by PythonTutor is the mechanism by which Python not only manages the names inside *one* function call but also for *many* potentially *simultaneous* calls, as revealed in [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb#Trivial-Example:-Countdown). It is the reason why we may re-use the same names for the parameters and variables inside both `average_evens()` and `average_wrong()` without Python mixing them up. So, as we already read in the [Zen of Python](https://www.python.org/dev/peps/pep-0020/), \"namespaces are one honking great idea\" (cf., `import this`), and a frame is just a special kind of namespace."
]
},
{
@ -732,7 +732,7 @@
{
"data": {
"text/plain": [
"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
"[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]"
]
},
"execution_count": 20,
@ -802,7 +802,7 @@
{
"data": {
"text/plain": [
"5.0"
"6.0"
]
},
"execution_count": 22,
@ -837,7 +837,7 @@
{
"data": {
"text/plain": [
"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
"[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]"
]
},
"execution_count": 23,
@ -859,7 +859,7 @@
"source": [
"The reason why everything works is that *every time* we (re-)assign an object to a variable *inside* a function with the `=` statement, this is done in the *local* scope by default. There are ways to change variables existing in an outer scope from within a function, but this is a rather advanced topic on its own.\n",
"\n",
"[PythonTutor](http://pythontutor.com/visualize.html#code=nums%20%3D%20%5B1,%202,%203,%204,%205,%206,%207,%208,%209,%2010%5D%0A%0Adef%20average_odds%28numbers%29%3A%0A%20%20%20%20nums%20%3D%20%5Bint%28n%29%20for%20n%20in%20numbers%5D%0A%20%20%20%20odds%20%3D%20%5Bn%20for%20n%20in%20nums%20if%20n%20%25%202%20!%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28odds%29%20/%20len%28odds%29%0A%20%20%20%20return%20average%0A%0Arv%20%3D%20average_odds%28%5B1.0,%2010.0,%203.0,%2010.0,%205.0%5D%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows how *two* `nums` variables exist in *different* scopes pointing to *different* objects (cf., steps 14-25) when we execute `average_odds([1.0, 10.0, 3.0, 10.0, 5.0])`.\n",
"[PythonTutor](http://pythontutor.com/visualize.html#code=nums%20%3D%20%5B1,%202,%203,%204,%205,%206,%207,%208,%209,%2010,%2011,%2012%5D%0A%0Adef%20average_odds%28numbers%29%3A%0A%20%20%20%20nums%20%3D%20%5Bint%28n%29%20for%20n%20in%20numbers%5D%0A%20%20%20%20odds%20%3D%20%5Bn%20for%20n%20in%20nums%20if%20n%20%25%202%20!%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28odds%29%20/%20len%28odds%29%0A%20%20%20%20return%20average%0A%0Arv%20%3D%20average_odds%28%5B1.0,%2010.0,%203.0,%2010.0,%205.0%5D%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows how *two* `nums` variables exist in *different* scopes pointing to *different* objects (cf., steps 14-25) when we execute `average_odds([1.0, 10.0, 3.0, 10.0, 5.0])`.\n",
"\n",
"Variables whose names collide with the ones of variables in enclosing scopes - and the global scope is just the most enclosing scope - are said to **shadow** them.\n",
"\n",
@ -902,7 +902,7 @@
{
"data": {
"text/plain": [
"10"
"12"
]
},
"execution_count": 24,
@ -926,7 +926,7 @@
{
"data": {
"text/plain": [
"55"
"78"
]
},
"execution_count": 25,
@ -946,7 +946,7 @@
}
},
"source": [
"We may cast objects as a different type. For example, to \"convert\" a float or a text into an integer, we use the [int()](https://docs.python.org/3/library/functions.html#int) built-in. It creates a *new* object of type `int` from the provided `avg` or `\"6\"` objects that continue to exist in memory unchanged."
"We may cast objects as a different type. For example, to \"convert\" a float or a text into an integer, we use the [int()](https://docs.python.org/3/library/functions.html#int) built-in. It creates a *new* object of type `int` from the provided `avg` or `\"7\"` objects that continue to exist in memory unchanged."
]
},
{
@ -961,7 +961,7 @@
{
"data": {
"text/plain": [
"6.0"
"7.0"
]
},
"execution_count": 26,
@ -985,7 +985,7 @@
{
"data": {
"text/plain": [
"6"
"7"
]
},
"execution_count": 27,
@ -1009,7 +1009,7 @@
{
"data": {
"text/plain": [
"6"
"7"
]
},
"execution_count": 28,
@ -1018,7 +1018,7 @@
}
],
"source": [
"int(\"6\")"
"int(\"7\")"
]
},
{
@ -1029,7 +1029,7 @@
}
},
"source": [
"Observe that casting as an integer is different from rounding with the [round()](https://docs.python.org/3/library/functions.html#round) built-in function."
"Casting as an `int` object is different from rounding with the built-in [round()](https://docs.python.org/3/library/functions.html#round) function!"
]
},
{
@ -1102,18 +1102,18 @@
"outputs": [
{
"ename": "ValueError",
"evalue": "invalid literal for int() with base 10: 'six'",
"evalue": "invalid literal for int() with base 10: 'seven'",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-31-28b6aa9c0167>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"six\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mValueError\u001b[0m: invalid literal for int() with base 10: 'six'"
"\u001b[0;32m<ipython-input-31-af421b358f21>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"seven\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mValueError\u001b[0m: invalid literal for int() with base 10: 'seven'"
]
}
],
"source": [
"int(\"six\")"
"int(\"seven\")"
]
},
{
@ -1170,7 +1170,7 @@
}
},
"source": [
"So far, we have only specified one parameter in each of our user-defined functions. In [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb), however, we saw the built-in function [divmod()](https://docs.python.org/3/library/functions.html#divmod) take two arguments. And, the order of the numbers passed in mattered! Whenever we call a function and list its arguments in a comma separated manner, we say that we pass in the arguments by position or refer to them as **positional arguments**."
"So far, we have only specified one parameter in each of our user-defined functions. In [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#%28Arithmetic%29-Operators), however, we saw the built-in function [divmod()](https://docs.python.org/3/library/functions.html#divmod) take two arguments. And, the order of the numbers passed in mattered! Whenever we call a function and list its arguments in a comma separated manner, we say that we pass in the arguments by position or refer to them as **positional arguments**."
]
},
{
@ -1281,7 +1281,7 @@
{
"data": {
"text/plain": [
"12.0"
"14.0"
]
},
"execution_count": 36,
@ -1318,7 +1318,7 @@
{
"data": {
"text/plain": [
"12.0"
"14.0"
]
},
"execution_count": 37,
@ -1342,7 +1342,7 @@
{
"data": {
"text/plain": [
"12.0"
"14.0"
]
},
"execution_count": 38,
@ -1366,7 +1366,7 @@
{
"data": {
"text/plain": [
"12.0"
"14.0"
]
},
"execution_count": 39,
@ -1483,7 +1483,7 @@
}
},
"source": [
"The outcome of `average_evens(nums)` is, of course, still `6.0`."
"The outcome of `average_evens(nums)` is, of course, still `7.0`."
]
},
{
@ -1498,7 +1498,7 @@
{
"data": {
"text/plain": [
"6.0"
"7.0"
]
},
"execution_count": 43,
@ -1555,7 +1555,7 @@
}
},
"source": [
"Now we call the function either with or without the `scalar` argument.\n",
"Now, we call the function either with or without the `scalar` argument.\n",
"\n",
"If `scalar` is to be passed in, this may be done as either a positional or a keyword argument. Which of the two versions where `scalar` is `2` is more \"natural\" and faster to comprehend in a larger program?"
]
@ -1572,7 +1572,7 @@
{
"data": {
"text/plain": [
"6.0"
"7.0"
]
},
"execution_count": 45,
@ -1596,7 +1596,7 @@
{
"data": {
"text/plain": [
"12.0"
"14.0"
]
},
"execution_count": 46,
@ -1620,7 +1620,7 @@
{
"data": {
"text/plain": [
"12.0"
"14.0"
]
},
"execution_count": 47,
@ -1705,7 +1705,7 @@
{
"data": {
"text/plain": [
"6.0"
"7.0"
]
},
"execution_count": 49,
@ -1729,7 +1729,7 @@
{
"data": {
"text/plain": [
"12.0"
"14.0"
]
},
"execution_count": 50,
@ -1749,7 +1749,7 @@
}
},
"source": [
"If we pass in `scalar` as a positional argument instead, we obtain a `TypeError`."
"If we pass in `scalar` as a positional argument instead, we get a `TypeError`."
]
},
{
@ -1841,11 +1841,11 @@
}
},
"source": [
"If you think this is a rather pointless thing to do, you are absolutely correct!\n",
"If you think this is rather pointless to do, you are absolutely correct!\n",
"\n",
"We created a `function` object, dit *not* call it, and Python immediately forgot about it. So what's the point?\n",
"\n",
"To prove that a `lambda` expression creates a callable `function` object, we use the simple `=` statement to assign it to the variable `add_three`, which is really `add_three()` as per our convention from above."
"To show that a `lambda` expression creates a callable `function` object, we use the simple `=` statement to assign it to the variable `add_three`, which is really `add_three()` as per our convention from above."
]
},
{
@ -1928,7 +1928,7 @@
}
],
"source": [
"(lambda x: x + 3)(39) # this looks weird but will become very useful"
"(lambda x: x + 3)(39)"
]
},
{
@ -1939,9 +1939,9 @@
}
},
"source": [
"The main point of having functions without a name is to use them in a situation where we know ahead of time that we use the function *once* only.\n",
"The main point of having functions without a name is to use them in a situation where we know ahead of time that we use the function only *once*.\n",
"\n",
"Popular applications of lambda expressions are with the **map-filter-reduce** paradigm in Chapter 7 or when we do \"number crunching\" with **arrays** and **data frames** in Chapter 9."
"Popular applications of lambda expressions occur in combination with the **map-filter-reduce** paradigm or when we do \"number crunching\" with **arrays** and **data frames**. We look at both in detail in [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb)."
]
},
{
@ -2021,7 +2021,7 @@
"source": [
"The [math](https://docs.python.org/3/library/math.html) module provides non-trivial mathematical functions like $sin(x)$ and constants like $\\pi$ or $\\text{e}$.\n",
"\n",
"To make functions and variables defined \"somewhere else\" available in our current program, we must first **[import](https://docs.python.org/3/reference/simple_stmts.html#import)** them with the `import` statement. "
"To make functions and variables defined \"somewhere else\" available in our current program, we must first **import** them with the `import` statement (cf., [reference](https://docs.python.org/3/reference/simple_stmts.html#import)). "
]
},
{
@ -2084,7 +2084,7 @@
{
"data": {
"text/plain": [
"140519828111832"
"139646755983752"
]
},
"execution_count": 58,
@ -2356,7 +2356,7 @@
"source": [
"Observe how the arguments passed to functions do not need to be just variables or simple literals. Instead, we may pass in any *expression* that evaluates to a *new* object of the type the function expects.\n",
"\n",
"So just as a reminder from the expression vs. statement discussion in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb): An expression is *any* syntactically correct combination of variables and literals with operators. And the call operator `()` is yet another operator. So both of the next two code cells are just expressions! They have no permanent side effects in memory. We may execute them as often as we want *without* changing the state of the program (i.e., this Jupyter notebook).\n",
"So just as a reminder from the expression vs. statement discussion in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Expressions): An expression is *any* syntactically correct combination of variables and literals with operators. And the call operator `()` is yet another operator. So both of the next two code cells are just expressions! They have no permanent side effects in memory. We may execute them as often as we want *without* changing the state of the program (i.e., this Jupyter notebook).\n",
"\n",
"So, regarding the very next cell in particular: Although the `2 ** 2` creates a *new* object `4` in memory that is then immediately passed into the [math.sqrt()](https://docs.python.org/3/library/math.html#math.sqrt) function, once that function call returns, \"all is lost\" and the newly created `4` object is forgotten again, as well as the return value of [math.sqrt()](https://docs.python.org/3/library/math.html#math.sqrt)."
]
@ -2489,7 +2489,7 @@
}
},
"source": [
"Often, we need a random variable, for example, when we want to build a simulation program. The [random](https://docs.python.org/3/library/random.html) module in the [standard library](https://docs.python.org/3/library/index.html) often suffices for that."
"Often, we need a random variable, for example, when we want to build a simulation. The [random](https://docs.python.org/3/library/random.html) module in the [standard library](https://docs.python.org/3/library/index.html) often suffices for that."
]
},
{
@ -2537,7 +2537,7 @@
}
},
"source": [
"Besides the usual dunder-style attributes, the built-in [dir()](https://docs.python.org/3/library/functions.html#dir) function lists some attributes in an upper case naming convention and many others starting with a *single* underscore `_`. To understand the former, we must wait until Chapter 10, while the latter is explained further below."
"Besides the usual dunder-style attributes, the built-in [dir()](https://docs.python.org/3/library/functions.html#dir) function lists some attributes in an upper case naming convention and many others starting with a *single* underscore `_`. To understand the former, we must wait until Chapter 9, while the latter is explained further below."
]
},
{
@ -2697,7 +2697,7 @@
{
"data": {
"text/plain": [
"0.5291407120147841"
"0.21932911318720072"
]
},
"execution_count": 75,
@ -2732,7 +2732,7 @@
{
"data": {
"text/plain": [
"<bound method Random.choice of <random.Random object at 0x5609be5deba8>>"
"<bound method Random.choice of <random.Random object at 0x55a6c6c03af8>>"
]
},
"execution_count": 76,
@ -2781,7 +2781,7 @@
{
"data": {
"text/plain": [
"4"
"7"
]
},
"execution_count": 78,
@ -2801,7 +2801,7 @@
}
},
"source": [
"To reproduce the same random numbers in a simulation each time we run it, we set the **[random seed](https://en.wikipedia.org/wiki/Random_seed)**. It is good practice to do this at the beginning of a program or notebook. Then every time we re-start the program, we get the *same* random numbers again. This becomes very important, for example, when we employ machine learning algorithms that rely on randomization, like the infamous [Random Forest](https://en.wikipedia.org/wiki/Random_forest), and want to obtain **reproducable** results.\n",
"To reproduce the *same* random numbers in a simulation each time we run it, we set the **[random seed](https://en.wikipedia.org/wiki/Random_seed)**. It is good practice to do that at the beginning of a program or notebook. It becomes essential when we employ randomized machine learning algorithms, like the [Random Forest](https://en.wikipedia.org/wiki/Random_forest), and want to obtain **reproducible** results for publication in academic journals.\n",
"\n",
"The [random](https://docs.python.org/3/library/random.html) module provides the [random.seed()](https://docs.python.org/3/library/random.html#random.seed) function to do that."
]
@ -2927,9 +2927,9 @@
}
},
"source": [
"[numpy](http://www.numpy.org/) is the de-facto standard in the Python world for handling **array-like** data. That is a fancy word for data that can be put into a matrix or vector format. We look at it in depth in Chapter 9.\n",
"[numpy](http://www.numpy.org/) is the de-facto standard in the Python world for handling **array-like** data. That is a fancy word for data that can be put into a matrix or vector format. We look at it in depth in [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb).\n",
"\n",
"As [numpy](http://www.numpy.org/) is *not* in the [standard library](https://docs.python.org/3/library/index.html), it must be *manually* installed, for example, with the [pip](https://pip.pypa.io/en/stable/) tool. As mentioned in [Chapter 0](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/00_start_up.ipynb), to execute terminal commands from within a Jupyter notebook, we start a code cell with an exclamation mark.\n",
"As [numpy](http://www.numpy.org/) is *not* in the [standard library](https://docs.python.org/3/library/index.html), it must be *manually* installed, for example, with the [pip](https://pip.pypa.io/en/stable/) tool. As mentioned in [Chapter 0](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/00_start_up.ipynb#Markdown-vs.-Code-Cells), to execute terminal commands from within a Jupyter notebook, we start a code cell with an exclamation mark.\n",
"\n",
"If you are running this notebook with an installation of the [Anaconda Distribution](https://www.anaconda.com/distribution/), then [numpy](http://www.numpy.org/) is probably already installed. Running the cell below confirms that."
]
@ -3050,7 +3050,7 @@
{
"data": {
"text/plain": [
"array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])"
"array([ 7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4])"
]
},
"execution_count": 87,
@ -3111,7 +3111,7 @@
{
"data": {
"text/plain": [
"array([ 2, 4, 6, 8, 10, 12, 14, 16, 18, 20])"
"array([14, 22, 16, 10, 6, 24, 4, 12, 18, 20, 2, 8])"
]
},
"execution_count": 89,
@ -3146,7 +3146,7 @@
{
"data": {
"text/plain": [
"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
"[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4, 7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]"
]
},
"execution_count": 90,
@ -3181,7 +3181,7 @@
{
"data": {
"text/plain": [
"55"
"78"
]
},
"execution_count": 91,
@ -3205,7 +3205,7 @@
{
"data": {
"text/plain": [
"1"
"7"
]
},
"execution_count": 92,
@ -3389,7 +3389,7 @@
{
"data": {
"text/plain": [
"6.0"
"7.0"
]
},
"execution_count": 97,
@ -3413,7 +3413,7 @@
{
"data": {
"text/plain": [
"12.0"
"14.0"
]
},
"execution_count": 98,
@ -3433,7 +3433,7 @@
}
},
"source": [
"Packages are a generalization of modules, and we look at one in detail in Chapter 10. You may, however, already look at a [sample package](https://github.com/webartifex/intro-to-python/tree/master/sample_package) in the repository, which is nothing but a folder with *.py* files in it.\n",
"Packages are a generalization of modules, and we look at one in detail in Chapter 9. You may, however, already look at a [sample package](https://github.com/webartifex/intro-to-python/tree/master/sample_package) in the repository, which is nothing but a folder with *.py* files in it.\n",
"\n",
"As a further reading on modules and packages, we refer to the [official tutorial](https://docs.python.org/3/tutorial/modules.html)."
]

View file

@ -139,7 +139,7 @@
}
},
"source": [
"There are, however, cases where even well-behaved Python does not make us happy. [Chapter 5](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers.ipynb) provides more insights into this \"bug.\""
"There are, however, cases where even well-behaved Python does not make us happy. [Chapter 5](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers.ipynb#Imprecision) provides more insights into this \"bug.\""
]
},
{
@ -189,7 +189,7 @@
{
"data": {
"text/plain": [
"94906834637792"
"94711290794976"
]
},
"execution_count": 5,
@ -213,7 +213,7 @@
{
"data": {
"text/plain": [
"94906834637760"
"94711290794944"
]
},
"execution_count": 6,
@ -281,7 +281,7 @@
}
},
"source": [
"Let's not confuse the boolean `False` with `None`, another built-in object! We saw the latter before in [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb) as the *implicit* return value of a function without a `return` statement.\n",
"Let's not confuse the boolean `False` with `None`, another built-in object! We saw the latter before in [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb#Function-Definition) as the *implicit* return value of a function without a `return` statement.\n",
"\n",
"We might think of `None` in a boolean context indicating a \"maybe\" or even an \"unknown\" answer; however, for Python, there are no \"maybe\" or \"unknown\" objects, as we see further below!\n",
"\n",
@ -313,7 +313,7 @@
{
"data": {
"text/plain": [
"94906834624752"
"94711290781936"
]
},
"execution_count": 10,
@ -357,7 +357,7 @@
}
},
"source": [
"`True`, `False`, and `None` have the property that they each exist in memory only *once*. Objects designed this way are so-called **singletons**. This **[design pattern](https://en.wikipedia.org/wiki/Design_Patterns)** was originally developed to keep a program's memory usage at a minimum. It may only be employed in situations where we know that an object does *not* mutate its value (i.e., to re-use the bag analogy from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb), no flipping of $0$s and $1$s in the bag is allowed). In languages \"closer\" to the memory like C, we would have to code this singleton logic ourselves, but Python has this built in for *some* types.\n",
"`True`, `False`, and `None` have the property that they each exist in memory only *once*. Objects designed this way are so-called **singletons**. This **[design pattern](https://en.wikipedia.org/wiki/Design_Patterns)** was originally developed to keep a program's memory usage at a minimum. It may only be employed in situations where we know that an object does *not* mutate its value (i.e., to re-use the bag analogy from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Objects-vs.-Types-vs.-Values), no flipping of $0$s and $1$s in the bag is allowed). In languages \"closer\" to the memory like C, we would have to code this singleton logic ourselves, but Python has this built in for *some* types.\n",
"\n",
"We verify this with either the `is` operator or by comparing memory addresses."
]
@ -897,9 +897,11 @@
}
},
"source": [
"The operands of the logical operators do not need to be *boolean* expressions as defined above but may be *any* expression. If a sub-expression does *not* evaluate to an object of type `bool`, Python automatically casts it as such.\n",
"The operands of the logical operators do not need to be *boolean* expressions but may be *any* expression. If a sub-expression does *not* evaluate to an object of type `bool`, Python automatically casts it as such.\n",
"\n",
"For example, any non-zero numeric object becomes `True`. While this behavior allows writing more concise and thus more \"beautiful\" code, it is also a common source of confusion. `(x - 9)` is cast as `True` and then the overall expression evaluates to `True` as well."
"For example, any non-zero numeric object becomes `True`. While this behavior allows writing more concise and thus more \"beautiful\" code, it is also a common source of confusion.\n",
"\n",
"So, `(x - 9)` is cast as `True` and then the overall expression evaluates to `True` as well."
]
},
{
@ -1122,7 +1124,7 @@
}
},
"source": [
"Pythonistas often use the terms **truthy** or **falsy** to describe a non-boolean expression's behavior when used in place of a boolean one."
"Pythonistas use the terms **truthy** or **falsy** to describe a non-boolean expression's behavior when evaluated in a boolean context."
]
},
{
@ -1144,14 +1146,18 @@
}
},
"source": [
"When evaluating boolean expressions with logical operators in it, Python follows the **[short-circuiting](https://en.wikipedia.org/wiki/Short-circuit_evaluation)** strategy: First, the inner-most sub-expressions are evaluated. Second, with identical **[operator precedence](https://docs.python.org/3/reference/expressions.html#operator-precedence)**, evaluation goes from left to right. Once it is clear what the overall truth value is, no more sub-expressions are evaluated, and the result is *immediately* returned.\n",
"When evaluating expressions involving the `and` and `or` operators, Python follows the **[short-circuiting](https://en.wikipedia.org/wiki/Short-circuit_evaluation)** strategy: Once it is clear what the overall truth value is, no more sub-expressions are evaluated, and the result is *immediately* returned.\n",
"\n",
"In summary, data science practitioners must know *how* the following two generic expressions are evaluated:\n",
"Also, if such expressions are evaluated in a non-boolean context, the result is returned as is and *not* cast as a `bool` type.\n",
"\n",
"- `x or y`: The `y` expression is evaluated *only if* `x` evaluates to `False`, in which case `y` is returned; otherwise, `x` is returned *without* even looking at `y`.\n",
"- `x and y`: The `y` expression is evaluated *only if* `x` evaluates to `True`. Then, if `y` also evaluates to `True`, it is returned; otherwise, `x` is returned.\n",
"The two rules can be summarized as:\n",
"\n",
"Let's look at a couple of examples."
"- `x or y`: If `x` is truthy, it is returned *without* evaluating `y`. Otherwise, `y` is evaluated *and* returned.\n",
"- `x and y`: If `x` is falsy, it is returned *without* evaluating `y`. Otherwise, `y` is evaluated *and* returned.\n",
"\n",
"The rules may also be chained or combined.\n",
"\n",
"Let's look at a couple of examples below. To visualize which sub-expressions are evaluated, we define a helper function `expr()` that prints out the only argument it is passed before returning it."
]
},
{
@ -1163,32 +1169,6 @@
}
},
"outputs": [],
"source": [
"x = 0\n",
"y = 1\n",
"z = 2"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"We define a helper function `expr()` that prints out the only argument it is passed before returning it. With `expr()`, we can see if a sub-expression is evaluated or not."
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"def expr(arg):\n",
" \"\"\"Print and return the only argument.\"\"\"\n",
@ -1204,12 +1184,12 @@
}
},
"source": [
"With the `or` operator, the first sub-expression that evaluates to `True` is returned."
"With the `or` operator, the first truthy sub-expression is returned."
]
},
{
"cell_type": "code",
"execution_count": 38,
"execution_count": 37,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -1230,18 +1210,18 @@
"1"
]
},
"execution_count": 38,
"execution_count": 37,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"expr(x) or expr(y)"
"expr(0) or expr(1)"
]
},
{
"cell_type": "code",
"execution_count": 39,
"execution_count": 38,
"metadata": {
"slideshow": {
"slide_type": "-"
@ -1261,18 +1241,18 @@
"1"
]
},
"execution_count": 39,
"execution_count": 38,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"expr(y) or expr(z)"
"expr(1) or expr(2) # 2 is not evaluated due to short-circuiting"
]
},
{
"cell_type": "code",
"execution_count": 40,
"execution_count": 39,
"metadata": {
"slideshow": {
"slide_type": "-"
@ -1293,13 +1273,13 @@
"1"
]
},
"execution_count": 40,
"execution_count": 39,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"expr(x) or expr(y) or expr(z)"
"expr(0) or expr(1) or expr(2) # 2 is not evaluated due to short-circuiting"
]
},
{
@ -1310,12 +1290,12 @@
}
},
"source": [
"If all sub-expressions evaluate to `False`, the last one is the result."
"If all sub-expressions are falsy, the last one is returned."
]
},
{
"cell_type": "code",
"execution_count": 41,
"execution_count": 40,
"metadata": {
"slideshow": {
"slide_type": "fragment"
@ -1337,13 +1317,13 @@
"0"
]
},
"execution_count": 41,
"execution_count": 40,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"expr(False) or expr([]) or expr(x)"
"expr(False) or expr([]) or expr(0)"
]
},
{
@ -1354,12 +1334,12 @@
}
},
"source": [
"With the `and` operator, the first sub-expression that evaluates to `False` is returned."
"With the `and` operator, the first falsy sub-expression is returned."
]
},
{
"cell_type": "code",
"execution_count": 42,
"execution_count": 41,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -1373,6 +1353,38 @@
"Arg: 0\n"
]
},
{
"data": {
"text/plain": [
"0"
]
},
"execution_count": 41,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"expr(0) and expr(1) # 1 is not evaluated due to short-circuiting"
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Arg: 1\n",
"Arg: 0\n"
]
},
{
"data": {
"text/plain": [
@ -1385,7 +1397,7 @@
}
],
"source": [
"expr(x) and expr(y)"
"expr(1) and expr(0)"
]
},
{
@ -1417,40 +1429,7 @@
}
],
"source": [
"expr(y) and expr(x)"
]
},
{
"cell_type": "code",
"execution_count": 44,
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Arg: 2\n",
"Arg: 1\n",
"Arg: 0\n"
]
},
{
"data": {
"text/plain": [
"0"
]
},
"execution_count": 44,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"expr(z) and expr(y) and expr(x)"
"expr(1) and expr(0) and expr(2) # 2 is not evaluated due to short-circuiting"
]
},
{
@ -1461,12 +1440,12 @@
}
},
"source": [
"If all sub-expressions evaluate to `True`, the last one is returned."
"If all sub-expressions are truthy, the last one is returned."
]
},
{
"cell_type": "code",
"execution_count": 45,
"execution_count": 44,
"metadata": {
"slideshow": {
"slide_type": "fragment"
@ -1487,13 +1466,13 @@
"2"
]
},
"execution_count": 45,
"execution_count": 44,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"expr(y) and expr(z)"
"expr(1) and expr(2)"
]
},
{
@ -1526,24 +1505,24 @@
}
},
"source": [
"To write useful programs, we need to control the flow of execution, for example, to react to user input. The logic by which a program does that is referred to as **business logic**.\n",
"To write useful programs, we need to control the flow of execution, for example, to react to user input. The logic by which a program follows the rules from the \"real world\" is referred to as **[business logic](https://en.wikipedia.org/wiki/Business_logic)**, even if \"real world\" refers to the domain of mathematics and not a business application.\n",
"\n",
"One language construct to do so is the **[conditional statement](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement)**, or `if` statement for short. It consists of:\n",
"One language feature to do so is the `if` statement (cf., [reference](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement)). It consists of:\n",
"\n",
"- *one* mandatory `if`-clause,\n",
"- an *arbitrary* number of `elif`-clauses (i.e. \"else if\"), and\n",
"- an *arbitrary* number of `elif`-clauses (i.e., \"else if\"), and\n",
"- an *optional* `else`-clause.\n",
"\n",
"The `if`- and `elif`-clauses each specify one *boolean* expression, also called **condition**, while the `else`-clause serves as the \"catch everything else\" case.\n",
"The `if`- and `elif`-clauses each specify one *boolean* expression, also called **condition** in this context, while the `else`-clause serves as the \"catch everything else\" case.\n",
"\n",
"In terms of syntax, the header lines end with a colon, and the code blocks are indented.\n",
"In contrast to our intuitive interpretation in natural languages, only the code in *one* of the alternatives, also called **branches**, is executed. To be precise, it is always the code in the first clause whose condition evaluates to `True`.\n",
"\n",
"In contrast to our intuitive interpretation in natural languages, only the code in *one* of the alternatives, also called **branches**, is executed. To be precise, it is always the code in the first branch whose condition evaluates to `True`."
"In terms of syntax, the header lines end with a colon, and the code blocks are indented. Formally, any statement that is written across several lines is a **[compound statement](https://docs.python.org/3/reference/compound_stmts.html#compound-statements)**, the code blocks are called **suites** and belong to one header line, and the term **clause** refers to a header line and its suite as a whole. So far, we have seen three compound statements: `for`, `if`, and `def`. On the contrary, **[simple statements](https://docs.python.org/3/reference/simple_stmts.html#simple-statements)**, for example, `=`, `del`, or `return`, are written on *one* line."
]
},
{
"cell_type": "code",
"execution_count": 46,
"execution_count": 45,
"metadata": {
"code_folding": [],
"slideshow": {
@ -1557,7 +1536,7 @@
},
{
"cell_type": "code",
"execution_count": 47,
"execution_count": 46,
"metadata": {
"code_folding": [],
"slideshow": {
@ -1599,7 +1578,7 @@
},
{
"cell_type": "code",
"execution_count": 48,
"execution_count": 47,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -1612,7 +1591,7 @@
},
{
"cell_type": "code",
"execution_count": 49,
"execution_count": 48,
"metadata": {
"slideshow": {
"slide_type": "-"
@ -1637,7 +1616,7 @@
},
{
"cell_type": "code",
"execution_count": 50,
"execution_count": 49,
"metadata": {
"slideshow": {
"slide_type": "skip"
@ -1674,7 +1653,7 @@
},
{
"cell_type": "code",
"execution_count": 51,
"execution_count": 50,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -1717,7 +1696,7 @@
},
{
"cell_type": "code",
"execution_count": 52,
"execution_count": 51,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -1728,7 +1707,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"z is odd\n"
"z is positive\n"
]
}
],
@ -1766,7 +1745,7 @@
}
},
"source": [
"When all we do with an `if` statement is to assign an object to a variable according to a single true-or-false condition (i.e., a binary choice), there is a shortcut: We assign the variable the result of a so-called **conditional expression**, or `if` expression for short, instead.\n",
"When an `if` statement assigns an object to a variable according to a true-or-false condition (i.e., a binary choice), there is a shortcut: We assign the variable the result of a so-called **[conditional expression](https://docs.python.org/3/reference/expressions.html#conditional-expressions)**, or `if` expression for short, instead.\n",
"\n",
"Think of a situation where we evaluate a piece-wise functional relationship $y = f(x)$ at a given $x$, for example:"
]
@ -1790,7 +1769,7 @@
},
{
"cell_type": "code",
"execution_count": 53,
"execution_count": 52,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -1814,7 +1793,7 @@
},
{
"cell_type": "code",
"execution_count": 54,
"execution_count": 53,
"metadata": {
"slideshow": {
"slide_type": "-"
@ -1830,7 +1809,7 @@
},
{
"cell_type": "code",
"execution_count": 55,
"execution_count": 54,
"metadata": {
"slideshow": {
"slide_type": "-"
@ -1843,7 +1822,7 @@
"9"
]
},
"execution_count": 55,
"execution_count": 54,
"metadata": {},
"output_type": "execute_result"
}
@ -1860,12 +1839,12 @@
}
},
"source": [
"On the contrary, the `if` expression fits into one line. The main downside here is a potential loss in readability, in particular, if the functional relationship is not that simple."
"On the contrary, the `if` expression fits into one line. The main downside is a potential loss in readability, in particular, if the functional relationship is not that simple. Also, some practitioners do *not* like that the condition is in the middle of the expression."
]
},
{
"cell_type": "code",
"execution_count": 56,
"execution_count": 55,
"metadata": {
"slideshow": {
"slide_type": "fragment"
@ -1878,7 +1857,7 @@
},
{
"cell_type": "code",
"execution_count": 57,
"execution_count": 56,
"metadata": {
"slideshow": {
"slide_type": "skip"
@ -1891,7 +1870,7 @@
"9"
]
},
"execution_count": 57,
"execution_count": 56,
"metadata": {},
"output_type": "execute_result"
}
@ -1908,12 +1887,12 @@
}
},
"source": [
"In this example, however, the most elegant solution would be to use the built-in [max()](https://docs.python.org/3/library/functions.html#max) function."
"In this example, however, the most elegant solution is to use the built-in [max()](https://docs.python.org/3/library/functions.html#max) function."
]
},
{
"cell_type": "code",
"execution_count": 58,
"execution_count": 57,
"metadata": {
"slideshow": {
"slide_type": "fragment"
@ -1926,7 +1905,7 @@
},
{
"cell_type": "code",
"execution_count": 59,
"execution_count": 58,
"metadata": {
"slideshow": {
"slide_type": "skip"
@ -1939,7 +1918,7 @@
"9"
]
},
"execution_count": 59,
"execution_count": 58,
"metadata": {},
"output_type": "execute_result"
}
@ -1948,17 +1927,6 @@
"y"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Conditional expressions may not only be used in the way described in this section. We already saw them as part of a *list comprehension* in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) and [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb) and revisit this in Chapter 7 in greater detail."
]
},
{
"cell_type": "markdown",
"metadata": {
@ -1980,14 +1948,14 @@
"source": [
"In the previous two chapters, we encountered a couple of *runtime* errors. A natural urge we might have after reading about conditional statements is to write code that somehow reacts to the occurrence of such exceptions. All we need is a way to formulate a condition for that.\n",
"\n",
"For sure, this is such a common thing to do that Python provides a language construct for it, namely the `try` [statement](https://docs.python.org/3/reference/compound_stmts.html#the-try-statement).\n",
"For sure, this is such a common thing to do that Python provides a language construct for it, namely the compound `try` statement (cf., [reference](https://docs.python.org/3/reference/compound_stmts.html#the-try-statement)).\n",
"\n",
"In its simplest form, it comes with just two branches: `try` and `except`. The following tells Python to execute the code in the `try`-branch, and if *anything* goes wrong, continue in the `except`-branch instead of **raising** an error to us. Of course, if nothing goes wrong, the `except`-branch is *not* executed."
"In its simplest form, it comes with just two clauses: `try` and `except`. The following tells Python to execute the code in the `try`-clause, and if *anything* goes wrong, continue in the `except`-clause instead of **raising** an error to us. Of course, if nothing goes wrong, the `except`-clause is *not* executed."
]
},
{
"cell_type": "code",
"execution_count": 60,
"execution_count": 59,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -2000,7 +1968,7 @@
},
{
"cell_type": "code",
"execution_count": 61,
"execution_count": 60,
"metadata": {
"slideshow": {
"slide_type": "-"
@ -2030,16 +1998,16 @@
}
},
"source": [
"However, it is good practice *not* to **handle** *any* possible exception but only the ones we may *expect* from the code in the `try`-branch. The reasoning why this is done is a bit involved. We only remark that the codebase becomes easier to understand as we communicate to any human reader what could go wrong during execution in an *explicit* way. Python comes with a lot of [built-in exceptions](https://docs.python.org/3/library/exceptions.html#concrete-exceptions) that we should familiarize ourselves with.\n",
"However, it is good practice *not* to **handle** *any* possible exception but only the ones we may *expect* from the code in the `try`-clause. The reasoning why this is done is a bit involved. We only remark that the codebase becomes easier to understand as we communicate to any human reader what could go wrong during execution in an *explicit* way. Python comes with a lot of [built-in exceptions](https://docs.python.org/3/library/exceptions.html#concrete-exceptions) that we should familiarize ourselves with.\n",
"\n",
"Another good practice is to always keep the code in the `try`-branch short to not *accidentally* handle an exception we do *not* want to handle.\n",
"Another good practice is to always keep the code in the `try`-clause short to not *accidentally* handle an exception we do *not* want to handle.\n",
"\n",
"In the example, we are dividing numbers and may expect a `ZeroDivisionError`."
]
},
{
"cell_type": "code",
"execution_count": 62,
"execution_count": 61,
"metadata": {
"slideshow": {
"slide_type": "fragment"
@ -2069,16 +2037,16 @@
}
},
"source": [
"Often, we may have to run some code *independent* of an exception occurring, for example, to close a connection to a database. To achieve that, we add a `finally`-branch to the `try` statement.\n",
"Often, we may have to run some code *independent* of an exception occurring, for example, to close a connection to a database. To achieve that, we add a `finally`-clause to the `try` statement.\n",
"\n",
"Similarly, we may have to run some code *only if* no exception occurs, but we do not want to put it in the `try`-branch as per the good practice mentioned above. To achieve that, we add an `else`-branch to the `try` statement.\n",
"Similarly, we may have to run some code *only if* no exception occurs, but we do not want to put it in the `try`-clause as per the good practice mentioned above. To achieve that, we add an `else`-clause to the `try` statement.\n",
"\n",
"To showcase everything together, we look at one last example. To spice it up a bit, we randomize the input. So run the cell several times and see for yourself."
]
},
{
"cell_type": "code",
"execution_count": 63,
"execution_count": 62,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -2089,7 +2057,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"Yes, division worked smoothly.\n",
"Oops. Division by 0. How does that work?\n",
"I am always printed\n"
]
}

View file

@ -8,7 +8,7 @@
}
},
"source": [
"# Chapter 4: Iteration"
"# Chapter 4: Recursion & Looping"
]
},
{
@ -859,7 +859,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"39.9 µs ± 5.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
"36.5 µs ± 2.47 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
]
}
],
@ -881,7 +881,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"1.61 ms ± 8.84 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
"1.58 ms ± 20.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
]
}
],
@ -903,7 +903,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"199 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
"194 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
]
}
],
@ -925,7 +925,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"2.21 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
"2.15 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
]
}
],
@ -947,7 +947,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"5.8 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
"5.59 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
]
}
],
@ -4107,7 +4107,7 @@
"source": [
"We use the built-in [isinstance()](https://docs.python.org/3/library/functions.html#isinstance) function to make sure `factorial()` is called with an `int` object as the argument. We further **validate the input** by verifying that the integer is non-negative.\n",
"\n",
"Meanwhile, we also see how we manually raise exceptions with the `raise` [statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement), another way of controlling the flow of execution.\n",
"Meanwhile, we also see how we manually raise exceptions with the `raise` statement (cf., [reference](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement)), another way of controlling the flow of execution.\n",
"\n",
"The first two branches in the revised `factorial()` function act as **guardians** ensuring that the code does not produce *unexpected* runtime errors: Errors may be expected when mentioned in the docstring.\n",
"\n",
@ -4289,7 +4289,7 @@
}
},
"source": [
"With everything *officially* introduced so far (i.e., without the introductory example in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) that only served as an overview), Python would be what is called **[Turing complete](https://en.wikipedia.org/wiki/Turing_completeness)**. That means that anything that could be formulated as an algorithm could be expressed with all the language features we have seen. Note that, in particular, we have *not* yet formally *introduced* the `for` and `while` statements!"
"With everything *officially* introduced so far, Python would be what is called **[Turing complete](https://en.wikipedia.org/wiki/Turing_completeness)**. That means that anything that could be formulated as an algorithm could be expressed with all the language features we have seen. Note that, in particular, we have *not* yet formally *introduced* the `for` and `while` statements!"
]
},
{
@ -4322,7 +4322,7 @@
}
},
"source": [
"Whereas functions combined with `if` statements suffice to model any repetitive logic, Python comes with a `while` [statement](https://docs.python.org/3/reference/compound_stmts.html#the-while-statement) that often makes it easier to implement iterative ideas.\n",
"Whereas functions combined with `if` statements suffice to model any repetitive logic, Python comes with a compound `while` statement (cf., [reference](https://docs.python.org/3/reference/compound_stmts.html#the-while-statement)) that often makes it easier to implement iterative ideas.\n",
"\n",
"It consists of a header line with a boolean expression followed by an indented code block. Before the first and after every execution of the code block, the boolean expression is evaluated, and if it is (still) equal to `True`, the code block runs (again). Eventually, some variable referenced in the boolean expression is changed in the code block such that the condition becomes `False`.\n",
"\n",
@ -4792,9 +4792,9 @@
"source": [
"Recursion and the `while` statement are two sides of the same coin. Disregarding that in the case of recursion Python internally faces some additional burden for managing the stack of frames in memory, both approaches lead to the *same* computational steps in memory. More importantly, we can re-formulate a recursive implementation in an iterative way and vice versa despite one of the two ways often \"feeling\" a lot more natural given a particular problem.\n",
"\n",
"So how does the `for` [statement](https://docs.python.org/3/reference/compound_stmts.html#the-for-statement) we saw in the very first example in this book fit into this picture? It is a *redundant* language construct to provide a *shorter* and more *convenient* syntax for common applications of the `while` statement. In programming, such additions to a language are called **syntactic sugar**. A cup of tea tastes better with sugar, but we may drink tea without sugar too.\n",
"So how does the compound `for` statement (cf., [reference](https://docs.python.org/3/reference/compound_stmts.html#the-for-statement)) in this book's very first example fit into this picture? It is a *redundant* language construct to provide a *shorter* and more *convenient* syntax for common applications of the `while` statement. In programming, such additions to a language are called **syntactic sugar**. A cup of tea tastes better with sugar, but we may drink tea without sugar too.\n",
"\n",
"Consider the following `numbers` list. Without the `for` statement, we have to manage a temporary **index variable** `i` to loop over all the elements and also obtain the individual elements with the `[]` operator in each iteration of the loop."
"Consider `elements` below. Without the `for` statement, we have to manage a temporary **index variable**, `index`, to loop over all the elements and also obtain the individual elements with the `[]` operator in each iteration of the loop."
]
},
{
@ -4807,7 +4807,7 @@
},
"outputs": [],
"source": [
"numbers = [0, 1, 2, 3, 4]"
"elements = [0, 1, 2, 3, 4]"
]
},
{
@ -4828,12 +4828,12 @@
}
],
"source": [
"i = 0\n",
"while i < len(numbers):\n",
" number = numbers[i]\n",
" print(number, end=\" \")\n",
" i += 1\n",
"del i"
"index = 0\n",
"while index < len(elements):\n",
" element = elements[index]\n",
" print(element, end=\" \")\n",
" index += 1\n",
"del index"
]
},
{
@ -4865,8 +4865,8 @@
}
],
"source": [
"for number in numbers:\n",
" print(number, end=\" \")"
"for element in elements:\n",
" print(element, end=\" \")"
]
},
{
@ -4877,7 +4877,7 @@
}
},
"source": [
"For sequences of integers, the [range()](https://docs.python.org/3/library/functions.html#func-range) built-in makes the `for` statement even more convenient: It creates a list-like object of type `range` that generates integers \"on the fly,\" and we look closely at the underlying effects in memory in Chapter 7."
"For sequences of integers, the [range()](https://docs.python.org/3/library/functions.html#func-range) built-in makes the `for` statement even more convenient: It creates a `list`-like object of type `range` that generates integers \"on the fly,\" and we look closely at the underlying effects in memory in [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb#Mapping)."
]
},
{
@ -4898,8 +4898,8 @@
}
],
"source": [
"for number in range(5):\n",
" print(number, end=\" \")"
"for element in range(5):\n",
" print(element, end=\" \")"
]
},
{
@ -4955,8 +4955,8 @@
}
],
"source": [
"for number in [1, 3, 5, 7, 9]:\n",
" print(number, end=\" \")"
"for element in [1, 3, 5, 7, 9]:\n",
" print(element, end=\" \")"
]
},
{
@ -4977,8 +4977,8 @@
}
],
"source": [
"for number in range(1, 10, 2):\n",
" print(number, end=\" \")"
"for element in range(1, 10, 2):\n",
" print(element, end=\" \")"
]
},
{
@ -5008,13 +5008,13 @@
"\n",
"Now, just as we classify objects by their types, we also classify these **concrete data types** (e.g., `int`, `float`, `str`, or `list`) into **abstract concepts**.\n",
"\n",
"We did this already in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) when we described a `list` object as \"some sort of container that holds [...] pointers to other objects\". So, abstractly speaking, **containers** are any objects that are \"composed\" of other objects and also \"manage\" how these objects are organized. `list` objects, for example, have the property that they model an internal order associated with its elements. There exist, however, other container types, many of which do *not* come with an order. So, containers primarily \"contain\" other objects and have *nothing* to do with looping.\n",
"We did this already in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Who-am-I?-And-how-many?) when we described a `list` object as \"some sort of container that holds [...] pointers to other objects\". So, abstractly speaking, **containers** are any objects that are \"composed\" of other objects and also \"manage\" how these objects are organized. `list` objects, for example, have the property that they model an internal order associated with its elements. There exist, however, other container types, many of which do *not* come with an order. So, containers primarily \"contain\" other objects and have *nothing* to do with looping.\n",
"\n",
"On the contrary, the abstract concept of **iterables** is all about looping: Any object that we can loop over is, by definition, an iterable. So, `range` objects, for example, are iterables that do *not* contain other objects. Moreover, looping does *not* have to occur in a *predictable* order, although this is the case for both `list` and `range` objects.\n",
"\n",
"Typically, containers are iterables, and iterables are containers. Yet, only because these two concepts coincide often, we must not think of them as the same. Chapter 10 finally gives an explanation as to how abstract concepts are implemented and play together.\n",
"Typically, containers are iterables, and iterables are containers. Yet, only because these two concepts coincide often, we must not think of them as the same. Chapter 9 finally gives an explanation as to how abstract concepts are implemented and play together.\n",
"\n",
"So, `list` objects like `first_names` below are iterable containers. They implement even more abstract concepts, as Chapter 7 reveals."
"So, `list` objects like `first_names` below are iterable containers. They implement even more abstract concepts, as [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb#Collections-vs.-Sequences) reveals."
]
},
{
@ -5097,7 +5097,7 @@
}
},
"source": [
"The cell below shows the *exact* workings of the `in` operator: Although `3.0` is *not* contained in `numbers`, it evaluates equal to the `3` that is, which is why the following expression evaluates to `True`. So, while we could colloquially say that `numbers` \"contains\" `3.0`, it actually does not."
"The cell below shows the *exact* workings of the `in` operator: Although `3.0` is *not* contained in `elements`, it evaluates equal to the `3` that is, which is why the following expression evaluates to `True`. So, while we could colloquially say that `elements` \"contains\" `3.0`, it actually does not."
]
},
{
@ -5121,7 +5121,7 @@
}
],
"source": [
"3.0 in numbers"
"3.0 in elements"
]
},
{
@ -5165,7 +5165,7 @@
}
},
"source": [
"If we must have an index variable in the loop's body, we use the [enumerate()](https://docs.python.org/3/library/functions.html#enumerate) built-in that takes an *iterable* as its argument and then generates a \"stream\" of \"pairs\" consisting of an index variable and an object provided by the iterable. There is *no* need to ever revert to the `while` statement to loop over an iterable object."
"If we must have an index variable in the loop's body, we use the [enumerate()](https://docs.python.org/3/library/functions.html#enumerate) built-in that takes an *iterable* as its argument and then generates a \"stream\" of \"pairs\" of an index variable, `i` below, and an object provided by the iterable, `name`, separated by a `,`. There is *no* need to ever revert to the `while` statement with an explicitly managed index variable to loop over an iterable object."
]
},
{
@ -5288,7 +5288,7 @@
}
},
"source": [
"In contrast to its recursive counterpart, the iterative `fibonacci()` function below is somewhat harder to read. For example, it is not so obvious as to how many iterations through the `for`-loop we need to make when implementing it. There is an increased risk of making an *off-by-one* error. Moreover, we need to track a `temp` variable along, at least until we have worked through Chapter 7. Do you understand what `temp` does?\n",
"In contrast to its recursive counterpart, the iterative `fibonacci()` function below is somewhat harder to read. For example, it is not so obvious as to how many iterations through the `for`-loop we need to make when implementing it. There is an increased risk of making an *off-by-one* error. Moreover, we need to track a `temp` variable along.\n",
"\n",
"However, one advantage of calculating Fibonacci numbers in a **forward** fashion with a `for` statement is that we could list the entire sequence in ascending order as we calculate the desired number. To show this, we added `print()` statements in `fibonacci()` below.\n",
"\n",
@ -5550,7 +5550,7 @@
}
},
"source": [
"Let's say we have a `numbers` list and want to check if the square of at least one of its elements is above a `threshold` of `100`."
"Let's say we have a `samples` list and want to check if the square of at least one of its elements is above a `threshold` of `100`."
]
},
{
@ -5563,7 +5563,7 @@
},
"outputs": [],
"source": [
"numbers = [3, 7, 2, 9, 11, 4, 7, 9, 4, 5]"
"samples = [3, 7, 2, 9, 11, 4, 7, 9, 4, 5]"
]
},
{
@ -5587,7 +5587,7 @@
}
},
"source": [
"A first naive implementation could look like this: We loop over *every* element in `numbers` and set an **indicator variable** `is_above`, initialized as `False`, to `True` once we encounter an element satisfying the search condition."
"A first naive implementation could look like this: We loop over *every* element in `samples` and set an **indicator variable** `is_above`, initialized as `False`, to `True` once we encounter an element satisfying the search condition."
]
},
{
@ -5603,22 +5603,22 @@
"name": "stdout",
"output_type": "stream",
"text": [
"3 7 2 9 11 4 7 9 4 5 => at least one number's square is above 100\n"
"3 7 2 9 11 4 7 9 4 5 => at least one sample's square is above 100\n"
]
}
],
"source": [
"is_above = False\n",
"\n",
"for number in numbers:\n",
" print(number, end=\" \") # added for didactical purposes\n",
" if number ** 2 > threshold:\n",
"for sample in samples:\n",
" print(sample, end=\" \") # added for didactical purposes\n",
" if sample ** 2 > threshold:\n",
" is_above = True\n",
"\n",
"if is_above:\n",
" print(\"=> at least one number's square is above\", threshold)\n",
" print(\"=> at least one sample's square is above\", threshold)\n",
"else:\n",
" print(\"=> no number's square is above\", threshold)"
" print(\"=> no sample's square is above\", threshold)"
]
},
{
@ -5629,11 +5629,11 @@
}
},
"source": [
"This implementation is *inefficient* as even if the *first* element in `numbers` has a square greater than `100`, we loop until the last element: This could take a long time for a big list.\n",
"This implementation is *inefficient* as even if the *first* element in `samples` has a square greater than `100`, we loop until the last element: This could take a long time for a big list.\n",
"\n",
"Moreover, we must initialize `is_above` *before* the `for`-loop and write an `if`-`else`-logic *after* it to check for the result. The actual business logic is *not* clear right away.\n",
"\n",
"Luckily, Python provides a `break` [statement](https://docs.python.org/3/reference/simple_stmts.html#the-break-statement) that lets us stop the `for`-loop in any iteration of the loop. Conceptually, it is yet another means of controlling the flow of execution."
"Luckily, Python provides the `break` statement (cf., [reference](https://docs.python.org/3/reference/simple_stmts.html#the-break-statement)) that lets us stop the `for`-loop in any iteration of the loop. Conceptually, it is yet another means of controlling the flow of execution."
]
},
{
@ -5649,23 +5649,23 @@
"name": "stdout",
"output_type": "stream",
"text": [
"3 7 2 9 11 => at least one number's square is above 100\n"
"3 7 2 9 11 => at least one sample's square is above 100\n"
]
}
],
"source": [
"is_above = False\n",
"\n",
"for number in numbers:\n",
" print(number, end=\" \") # added for didactical purposes\n",
" if number ** 2 > threshold:\n",
"for sample in samples:\n",
" print(sample, end=\" \") # added for didactical purposes\n",
" if sample ** 2 > threshold:\n",
" is_above = True\n",
" break\n",
"\n",
"if is_above:\n",
" print(\"=> at least one number's square is above\", threshold)\n",
" print(\"=> at least one sample's square is above\", threshold)\n",
"else:\n",
" print(\"=> no number's square is above\", threshold)"
" print(\"=> no sample's square is above\", threshold)"
]
},
{
@ -5687,7 +5687,7 @@
}
},
"source": [
"### The `for`-`else` Clause"
"### The `for`-`else`-Clause"
]
},
{
@ -5698,7 +5698,7 @@
}
},
"source": [
"To express the logic in a prettier way, we add an `else`-clause at the end of the `for`-loop. The `else`-branch is executed *iff* the `for`-loop is *not* stopped with a `break` statement **prematurely** (i.e., *before* reaching the *last* iteration in the loop). The word \"else\" implies a somewhat unintuitive meaning and had better been named a `then`-clause. In most scenarios, however, the `else`-clause logically goes together with some `if` statement in the `for`-loop's body.\n",
"To express the logic in a prettier way, we add an `else`-clause at the end of the `for`-loop (cf., [reference](https://docs.python.org/3/reference/compound_stmts.html#the-for-statement)). The `else`-clause is executed *iff* the `for`-loop is *not* stopped with a `break` statement **prematurely** (i.e., *before* reaching the *last* iteration in the loop). The word \"else\" implies a somewhat unintuitive meaning and had better been named a `then`-clause. In most scenarios, however, the `else`-clause logically goes together with some `if` statement in the `for`-loop's body.\n",
"\n",
"Overall, the code's expressive power increases. Not many programming languages support a `for`-`else`-branching, which turns out to be very useful in practice."
]
@ -5727,23 +5727,23 @@
"name": "stdout",
"output_type": "stream",
"text": [
"3 7 2 9 11 => at least one number's square is above 100\n"
"3 7 2 9 11 => at least one sample's square is above 100\n"
]
}
],
"source": [
"for number in numbers:\n",
" print(number, end=\" \") # added for didactical purposes\n",
" if number ** 2 > threshold:\n",
"for sample in samples:\n",
" print(sample, end=\" \") # added for didactical purposes\n",
" if sample ** 2 > threshold:\n",
" is_above = True\n",
" break\n",
"else:\n",
" is_above = False\n",
"\n",
"if is_above:\n",
" print(\"=> at least one number's square is above\", threshold)\n",
" print(\"=> at least one sample's square is above\", threshold)\n",
"else:\n",
" print(\"=> no number's square is above\", threshold)"
" print(\"=> no sample's square is above\", threshold)"
]
},
{
@ -5770,18 +5770,18 @@
"name": "stdout",
"output_type": "stream",
"text": [
"3 7 2 9 11 => at least one number's square is above 100\n"
"3 7 2 9 11 => at least one sample's square is above 100\n"
]
}
],
"source": [
"for number in numbers:\n",
" print(number, end=\" \") # added for didactical purposes\n",
" if number ** 2 > threshold:\n",
" print(\"=> at least one number's square is above\", threshold)\n",
"for sample in samples:\n",
" print(sample, end=\" \") # added for didactical purposes\n",
" if sample ** 2 > threshold:\n",
" print(\"=> at least one sample's square is above\", threshold)\n",
" break\n",
"else:\n",
" print(\"=> no number's square is above\", threshold)"
" print(\"=> no sample's square is above\", threshold)"
]
},
{
@ -5792,7 +5792,7 @@
}
},
"source": [
"Of course, if we set the `threshold` an element's square has to pass higher, for example, to `200`, we have to loop through the entire `numbers` list. There is *no way* to optimize this **[linear search](https://en.wikipedia.org/wiki/Linear_search)** further, at least as long as we model `numbers` as a `list` object. More advanced data types, however, exist that mitigate that downside."
"Of course, if we set the `threshold` an element's square has to pass higher, for example, to `200`, we have to loop over all `samples`. There is *no way* to optimize this **[linear search](https://en.wikipedia.org/wiki/Linear_search)** further, at least as long as we model `samples` as a `list` object. More advanced data types, however, exist that mitigate that downside."
]
},
{
@ -5808,20 +5808,20 @@
"name": "stdout",
"output_type": "stream",
"text": [
"3 7 2 9 11 4 7 9 4 5 => no number's square is above 200\n"
"3 7 2 9 11 4 7 9 4 5 => no sample's square is above 200\n"
]
}
],
"source": [
"threshold = 200\n",
"\n",
"for number in numbers:\n",
" print(number, end=\" \") # added for didactical purposes\n",
" if number ** 2 > threshold:\n",
" print(\"=> at least one number's square is above\", threshold)\n",
"for sample in samples:\n",
" print(sample, end=\" \") # added for didactical purposes\n",
" if sample ** 2 > threshold:\n",
" print(\"=> at least one sample's square is above\", threshold)\n",
" break\n",
"else:\n",
" print(\"=> no number's square is above\", threshold)"
" print(\"=> no sample's square is above\", threshold)"
]
},
{
@ -5843,7 +5843,7 @@
}
},
"source": [
"Often, we process some iterable with numeric data, for example, a list of numbers as in the introductory example in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) or, more realistically, data from a CSV file with many rows and columns.\n",
"Often, we process some iterable with numeric data, for example, a list of `numbers` as in this book's introductory example in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Example:-Averaging-Even-Numbers) or, more realistically, data from a CSV file with many rows and columns.\n",
"\n",
"Processing numeric data usually comes down to operations that may be grouped into one of the following three categories:\n",
"\n",
@ -5851,7 +5851,7 @@
"- **filtering**: throw away individual samples (e.g., statistical outliers)\n",
"- **reducing**: collect individual samples into summary statistics\n",
"\n",
"We study this **map-filter-reduce** paradigm extensively in Chapter 7 after introducing more advanced data types that are needed to work with \"big\" data.\n",
"We study this **map-filter-reduce** paradigm extensively in [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb#The-Map-Filter-Reduce-Paradigm) after introducing more advanced data types that are needed to work with \"big\" data.\n",
"\n",
"In the remainder of this section, we focus on *filtering out* some samples within a `for`-loop."
]
@ -5875,7 +5875,7 @@
}
},
"source": [
"Calculate the sum of all even numbers from $1$ through $12$ after squaring them and adding $1$ to the squares:\n",
"Calculate the sum of all even numbers in `[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]` after squaring them and adding `1` to the squares:\n",
"\n",
"- **\"all\"** => loop over an iterable\n",
"- **\"even\"** => *filter* out the odd numbers\n",
@ -5893,7 +5893,7 @@
},
"outputs": [],
"source": [
"numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]"
"numbers = [7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]"
]
},
{
@ -5909,7 +5909,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"2 > 5 4 > 17 6 > 37 8 > 65 10 > 101 12 > 145 "
"8 > 65 12 > 145 2 > 5 6 > 37 10 > 101 4 > 17 "
]
},
{
@ -5926,11 +5926,11 @@
"source": [
"total = 0\n",
"\n",
"for x in numbers:\n",
" if x % 2 == 0: # only keep even numbers\n",
" y = (x ** 2) + 1\n",
" print(x, y, sep=\" > \", end=\" \") # added for didactical purposes\n",
" total += y\n",
"for number in numbers:\n",
" if number % 2 == 0: # only keep even numbers\n",
" square = (number ** 2) + 1\n",
" print(number, square, sep=\" > \", end=\" \") # added for didactical purposes\n",
" total += square\n",
"\n",
"total"
]
@ -5969,7 +5969,7 @@
}
},
"source": [
"Calculate the sum of every third and even number from $1$ through $12$ after squaring them and adding $1$ to the squares:\n",
"Calculate the sum of every third and even number in `[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]` after squaring them and adding `1` to the squares:\n",
"\n",
"- **\"every\"** => loop over an iterable\n",
"- **\"third\"** => *filter* out all numbers except every third\n",
@ -5990,7 +5990,7 @@
{
"data": {
"text/plain": [
"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]"
"[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]"
]
},
"execution_count": 78,
@ -6015,13 +6015,13 @@
"name": "stdout",
"output_type": "stream",
"text": [
"6 > 37 12 > 145 "
"8 > 65 12 > 145 4 > 17 "
]
},
{
"data": {
"text/plain": [
"182"
"227"
]
},
"execution_count": 79,
@ -6032,12 +6032,12 @@
"source": [
"total = 0\n",
"\n",
"for i, x in enumerate(numbers, start=1):\n",
"for i, number in enumerate(numbers, start=1):\n",
" if i % 3 == 0: # only keep every third number\n",
" if x % 2 == 0: # only keep even numbers\n",
" y = (x ** 2) + 1\n",
" print(x, y, sep=\" > \", end=\" \") # added for didactical purposes \n",
" total += y\n",
" if number % 2 == 0: # only keep even numbers\n",
" square = (number ** 2) + 1\n",
" print(number, square, sep=\" > \", end=\" \") # added for didactical purposes \n",
" total += square\n",
"\n",
"total"
]
@ -6052,7 +6052,7 @@
"source": [
"With already three levels of indentation, less horizontal space is available for the actual code block. Of course, one could flatten the two `if` statements with the logical `and` operator, as shown in [Chapter 3](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals.ipynb#The-if-Statement). Then, however, we trade off horizontal space against a more \"complex\" `if` logic, and this is *not* a real improvement.\n",
"\n",
"A Pythonista would instead make use of the `continue` [statement](https://docs.python.org/3/reference/simple_stmts.html#the-continue-statement) that causes a loop to jump into the next iteration skipping the rest of the code block.\n",
"A Pythonista would instead make use of the `continue` statement (cf., [reference](https://docs.python.org/3/reference/simple_stmts.html#the-continue-statement)) that causes a loop to jump into the next iteration skipping the rest of the code block.\n",
"\n",
"The revised code fragment below occupies more vertical space and less horizontal space: A *good* trade-off."
]
@ -6081,13 +6081,13 @@
"name": "stdout",
"output_type": "stream",
"text": [
"6 > 37 12 > 145 "
"8 > 65 12 > 145 4 > 17 "
]
},
{
"data": {
"text/plain": [
"182"
"227"
]
},
"execution_count": 80,
@ -6098,15 +6098,15 @@
"source": [
"total = 0\n",
"\n",
"for i, x in enumerate(numbers, start=1):\n",
"for i, number in enumerate(numbers, start=1):\n",
" if i % 3 != 0: # only keep every third number\n",
" continue\n",
" elif x % 2 != 0: # only keep even numbers\n",
" elif number % 2 != 0: # only keep even numbers\n",
" continue\n",
"\n",
" y = (x ** 2) + 1\n",
" print(x, y, sep=\" > \", end=\" \") # added for didactical purposes \n",
" total += y\n",
" square = (number ** 2) + 1\n",
" print(number, square, sep=\" > \", end=\" \") # added for didactical purposes \n",
" total += square\n",
"\n",
"total"
]

View file

@ -5,7 +5,7 @@
"metadata": {},
"source": [
"\n",
"# Chapter 4: Iteration"
"# Chapter 4: Recursion & Looping"
]
},
{

View file

@ -8,7 +8,7 @@
}
},
"source": [
"# Chapter 5: Numbers"
"# Chapter 5: Bits & Numbers"
]
},
{
@ -23,7 +23,7 @@
"\n",
"We start with the \"simple\" ones: Numeric types in this chapter and textual data in [Chapter 6](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/06_text.ipynb). An important fact that holds for all objects of these types is that they are **immutable**. To re-use the bag analogy from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Objects-vs.-Types-vs.-Values), this means that the $0$s and $1$s making up an object's *value* cannot be changed once the bag is created in memory, implying that any operation with or method on the object creates a *new* object in a *different* memory location.\n",
"\n",
"Chapters 7, 8, and 9 then cover the more \"complex\" data types, including, for example, the `list` type. Finally, Chapter 10 completes the picture by introducing language constructs to create custom types.\n",
"[Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb) and Chapter 8 then cover the more \"complex\" data types, including, for example, the `list` type. Finally, Chapter 9 completes the picture by introducing language constructs to create custom types.\n",
"\n",
"We have already seen many hints indicating that numbers are not as trivial to work with as it seems at first sight:\n",
"\n",
@ -92,7 +92,7 @@
{
"data": {
"text/plain": [
"139928698315984"
"140087541220560"
]
},
"execution_count": 2,
@ -2115,7 +2115,7 @@
{
"data": {
"text/plain": [
"139928698489496"
"140087541402072"
]
},
"execution_count": 63,
@ -2242,7 +2242,7 @@
}
],
"source": [
"1. # on the contrary, 1 creates an int object"
"1. # 1 without a dot creates an int object"
]
},
{
@ -5498,7 +5498,7 @@
{
"data": {
"text/plain": [
"139928697740272"
"140087540555856"
]
},
"execution_count": 176,
@ -6100,7 +6100,7 @@
}
},
"source": [
"## The Numeric Tower"
"## The Numerical Tower"
]
},
{
@ -6149,7 +6149,7 @@
"\n",
"For the other types, in particular, the `float` type, the implications of their imprecision are discussed in detail above.\n",
"\n",
"The abstract concepts behind the four outer-most mathematical sets are part of Python since [PEP 3141](https://www.python.org/dev/peps/pep-3141/) in 2007. The [numbers](https://docs.python.org/3/library/numbers.html) module in the [standard library](https://docs.python.org/3/library/index.html) defines what Pythonistas call the **numeric tower**, a collection of five **[abstract data types](https://en.wikipedia.org/wiki/Abstract_data_type)**, or **abstract base classes** as they are called in Python jargon:\n",
"The abstract concepts behind the four outer-most mathematical sets are part of Python since [PEP 3141](https://www.python.org/dev/peps/pep-3141/) in 2007. The [numbers](https://docs.python.org/3/library/numbers.html) module in the [standard library](https://docs.python.org/3/library/index.html) defines what programmers call the **[numerical tower](https://en.wikipedia.org/wiki/Numerical_tower)**, a collection of five **[abstract data types](https://en.wikipedia.org/wiki/Abstract_data_type)**, or **abstract base classes** as they are called in Python jargon:\n",
"\n",
"- `numbers.Number`: \"any number\" (cf., [documentation](https://docs.python.org/3/library/numbers.html#numbers.Number))\n",
"- `numbers.Complex`: \"all complex numbers\" (cf., [documentation](https://docs.python.org/3/library/numbers.html#numbers.Complex))\n",
@ -6712,7 +6712,7 @@
}
},
"source": [
"#### Example: [Factorial](https://en.wikipedia.org/wiki/Factorial) (revisited)"
"### Goose Typing"
]
},
{
@ -6723,7 +6723,9 @@
}
},
"source": [
"Replacing *concrete* data types with *abstract* ones is particularly valuable in the context of input validation: The revised version of the `factorial()` function below allows its user to *take advantage of duck typing*. If a real but non-integer argument `n` is passed in, `factorial()` tries to cast `n` as an `int` object with the [trunc()](https://docs.python.org/3/library/math.html#math.trunc) function from the [math](https://docs.python.org/3/library/math.html) module in the [standard library](https://docs.python.org/3/library/index.html). [trunc()](https://docs.python.org/3/library/math.html#math.trunc) cuts off all decimals and any *concrete* numeric type implementing the *abstract* `numbers.Real` type supports it (cf., [documentation](https://docs.python.org/3/library/numbers.html#numbers.Real))."
"Replacing *concrete* data types with *abstract* ones is particularly valuable in the context of input validation: The revised version of the `factorial()` function below allows its user to take advantage of *duck typing*: If a real but non-integer argument `n` is passed in, `factorial()` tries to cast `n` as an `int` object with the [trunc()](https://docs.python.org/3/library/math.html#math.trunc) function from the [math](https://docs.python.org/3/library/math.html) module in the [standard library](https://docs.python.org/3/library/index.html). [trunc()](https://docs.python.org/3/library/math.html#math.trunc) cuts off all decimals and any *concrete* numeric type implementing the *abstract* `numbers.Real` type supports it (cf., [documentation](https://docs.python.org/3/library/numbers.html#numbers.Real)).\n",
"\n",
"Two popular and distinguished Pythonistas, [Luciano Ramalho](https://github.com/ramalho) and [Alex Martelli](https://en.wikipedia.org/wiki/Alex_Martelli), coin the term **goose typing** to specifically mean using the built-in [isinstance()](https://docs.python.org/3/library/functions.html#isinstance) function with an *abstract base class* (cf., Chapter 11 in this [book](https://www.amazon.com/Fluent-Python-Concise-Effective-Programming/dp/1491946008) or this [summary](https://dgkim5360.github.io/blog/python/2017/07/duck-typing-vs-goose-typing-pythonic-interfaces/) thereof)."
]
},
{
@ -6763,6 +6765,17 @@
"math.trunc(1 / 10)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"#### Example: [Factorial](https://en.wikipedia.org/wiki/Factorial) (revisited)"
]
},
{
"cell_type": "code",
"execution_count": 211,
@ -6893,7 +6906,7 @@
}
},
"source": [
"With the keyword-only argument `strict`, we can control whether or not a passed in `float` object may be rounded. By default, this is not allowed."
"With the keyword-only argument `strict`, we can control whether or not a passed in `float` object may be rounded. By default, this is not allowed and results in a `TypeError`."
]
},
{
@ -7003,20 +7016,20 @@
},
"source": [
"There exist three numeric types in core Python:\n",
"- `int`: a perfect model for whole numbers (i.e., the set $\\mathbb{Z}$); inherently precise\n",
"- `int`: a near-perfect model for whole numbers (i.e., the set $\\mathbb{Z}$); inherently precise\n",
"- `float`: the \"gold\" standard to approximate real numbers (i.e., the set $\\mathbb{R}$); inherently imprecise\n",
"- `complex`: layer on top of the `float` type; therefore inherently imprecise\n",
"\n",
"Furthermore, the [standard library](https://docs.python.org/3/library/index.html) adds two more types that can be used as substitutes for `float` objects:\n",
"- `Decimal`: similar to `float` but allows customizing the precision; still inherently imprecise\n",
"- `Fraction`: a perfect model for rational numbers (i.e., the set $\\mathbb{Q}$); built on top of the `int` type and therefore inherently precise\n",
"- `Fraction`: a near-perfect model for rational numbers (i.e., the set $\\mathbb{Q}$); built on top of the `int` type and therefore inherently precise\n",
"\n",
"The *important* takeaways for the data science practitioner are:\n",
"\n",
"1. **Do not mix** precise and imprecise data types, and\n",
"2. actively expect `nan` results when working with `float` numbers as there are no **loud failures**.\n",
"\n",
"The **numeric tower** is Python's way of implementing the various **abstract** ideas of what a number is in mathematics."
"The **numerical tower** is Python's way of implementing the various **abstract** ideas of what numbers are in mathematics."
]
}
],

View file

@ -5,7 +5,7 @@
"metadata": {},
"source": [
"\n",
"# Chapter 5: Numbers"
"# Chapter 5: Bits & Numbers"
]
},
{
@ -156,7 +156,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q9**: How can **abstract data types**, for example, as defined in the **numeric tower**, be helpful in enabling **duck typing**?"
"**Q9**: How can **abstract data types**, for example, as defined in the **numerical tower**, be helpful in enabling **duck typing**?"
]
},
{

File diff suppressed because it is too large Load diff

View file

@ -5,7 +5,7 @@
"metadata": {},
"source": [
"\n",
"# Chapter 6: Text"
"# Chapter 6: Bytes & Text"
]
},
{
@ -54,7 +54,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q2**: What is a direct consequence of the `str` type's property of being **ordered**? What operations could we *not* do with it if it were *unordered*?"
"**Q2**: What is a direct consequence of the `str` type's property of being an **ordered** sequence? What operations could we *not* do with it if it were *unordered*?"
]
},
{
@ -87,7 +87,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q4**: Describe in your own words what we mean with **string interpolation**!"
"**Q4**: Describe in your own words what we mean by **string interpolation**!"
]
},
{
@ -115,7 +115,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q5**: **Triple-double** quotes `\"\"\"` and **triple-single** quotes `'''` create a *new* object of type `text` that model so-called **multi-line strings**."
"**Q5**: **Triple-double** quotes `\"\"\"` and **triple-single** quotes `'''` create a *new* object of type `text` that models so-called **multi-line strings**."
]
},
{
@ -143,7 +143,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q7**: Indexing into a `str` object with a *negative* index **fails silently**: It does *not* raise an error but also does *not* do anything useful."
"**Q7**: Indexing into a `str` object with a *negative* index **fails silently**: It does *neither* raise an error *nor* do anything useful."
]
},
{

10742
07_sequences.ipynb Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,389 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"# Chapter 7: Sequential Data"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Content Review"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Read [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb) of the book. Then work through the ten review questions."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Essay Questions "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Answer the following questions briefly with *at most* 300 characters per question!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q1**: We have seen **containers** and **iterables** before in [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb#Containers-vs.-Iterables). How do they relate to **sequences**? "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q2**: Explain the difference between a **mutable** and an **immutable** object in Python with an example!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q3**: What is the difference between a **shallow** and a **deep** copy of an object? How can one of them become a problem?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q4.1**: `tuple` objects have *two* primary usages. First, they can be used in place of `list` objects where **mutability** is *not* required. Second, we use them to model **records**.\n",
"\n",
"Describe why `tuple` objects are a suitable replacement for `list` objects in general!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q4.2**: What do we mean by a **record**? How are `tuple` objects suitable to model records? How can we integrate a **semantic** meaning when working with records?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q5**: With the [map()](https://docs.python.org/3/library/functions.html#map) and [filter()](https://docs.python.org/3/library/functions.html#filter) built-ins and the [reduce()](https://docs.python.org/3/library/functools.html#functools.reduce) function from the [functools](https://docs.python.org/3/library/functools.html) module in the [standard library](https://docs.python.org/3/library/index.html), we can replace many tedious `for`-loops and `if` statements. What are some advantages of doing so?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### True / False Questions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Motivate your answer with *one short* sentence!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q6**: `sequence` objects are *not* part of core Python but may be imported from the [standard library](https://docs.python.org/3/library/index.html)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q7**: Passing **mutable** objects as arguments to functions is not problematic because functions operate in a **local** scope without affecting the **global** scope."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q8**: The **map-filter-reduce** paradigm is an excellent mental concept to organize one's code with. Then, there is a good chance that a program can be **parallelized** if the data input grows."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q9**: `lambda` expressions are useful in the context of the **map-filter-reduce** paradigm, where we often do *not* re-use a `function` object more than once."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Coding Exercises"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Working with Lists"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q10.1**: Write a function `nested_sum()` that takes a `list` object as its argument, which contains other `list` objects with numbers, and adds up the numbers! Use `nested_numbers` below to test your function!\n",
"\n",
"Hint: You need at least one `for`-loop."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"nested_numbers = [[1, 2, 3], [4], [5], [6, 7], [8], [9]]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def nested_sum():\n",
" ..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"nested_sum(nested_numbers)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q10.2**: Provide a one-line expression to obtain the *same* result as `nested_sum()`!\n",
"\n",
"Hints: Use a *list comprehension*. You may want to use the built-in [sum()](https://docs.python.org/3/library/functions.html#sum) function several times."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q10.3**: Generalize `nested_sum()` into a function `mixed_sum()` that can process a \"mixed\" `list` object, which contains numbers and other `list` objects with numbers! Use `mixed_numbers` below for testing!\n",
"\n",
"Hints: Use the built-in [isinstance()](https://docs.python.org/3/library/functions.html#isinstance) function to check how an element is to be processed. Get extra credit for adhering to *goose typing*, as explained in [Chapter 5](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers.ipynb#Goose-Typing)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"mixed_numbers = [[1, 2, 3], 4, 5, [6, 7], 8, [9]]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def mixed_sum():\n",
" ..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"mixed_sum(nums)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q10.4.1**: Write a function `cum_sum()` that takes a `list` object with numbers as its argument and returns a *new* `list` object with the **cumulative sums** of these numbers! So, `sum_up` below, `[1, 2, 3, 4, 5]`, should return `[1, 3, 6, 10, 15]`.\n",
"\n",
"Hint: The idea behind is similar to the [cumulative distribution function](https://en.wikipedia.org/wiki/Cumulative_distribution_function) from statistics."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sum_up = [1, 2, 3, 4, 5]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def cum_sum():\n",
" ..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"cum_sum(sum_up)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q10.4.2**: We should always make sure that our functions also work in corner cases. What happens if your implementation of `cum_sum()` is called with an empty list `[]`? Make sure it handles that case *without* crashing! What would be a good return value in this corner case? Describe everything in the docstring.\n",
"\n",
"Hint: It is possible to write this without any extra input validation."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"cum_sum([])"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": false,
"sideBar": true,
"skip_h1_title": true,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": false,
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View file

@ -19,8 +19,9 @@ As such they can be viewed in a plain web browser:
- [02 - Functions & Modularization](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb)
- [03 - Conditionals & Exceptions](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals.ipynb)
- [04 - Recursion & Looping](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb)
- [05 - Numbers](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers.ipynb)
- [06 - Text](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/06_text.ipynb)
- [05 - Bits & Numbers](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers.ipynb)
- [06 - Bytes & Text](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/06_text.ipynb)
- [07 - Sequential Data](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb)
However, it is recommended that students **install Python and Jupyter
locally** and run the code in the notebooks on their own.
@ -125,8 +126,8 @@ are popular tools to automate the described management of virtual environments.
After activation for the first time, you must install the project's
**dependencies** (= the third-party packages needed to run the code), most
notably [Jupyter](https://pypi.org/project/jupyter/) in this project (the
"python -m" is often left out; if you have poetry installed, you may just
type `poetry install` instead).
"python -m" is often left out [but should not be](https://snarky.ca/why-you-should-use-python-m-pip/);
if you have poetry installed, you may just type `poetry install` instead).
- `python -m pip install -r requirements.txt`

6
lorem_ipsum.txt Normal file
View file

@ -0,0 +1,6 @@
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s
when an unknown printer took a galley of type and scrambled it to make a type
specimen book. It has survived not only five centuries but also the leap into
electronic typesetting, remaining essentially unchanged. It was popularised in
the 1960s with the release of Letraset sheets.