Streamline previous content
This commit is contained in:
parent
84e08d06f4
commit
250aa09209
7 changed files with 181 additions and 104 deletions
|
|
@ -19,7 +19,7 @@
|
|||
}
|
||||
},
|
||||
"source": [
|
||||
"In Chapter 1 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) 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",
|
||||
"\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)** can be created with the `def` statement. To extend an already familiar example, we re-use the introductory example from Chapter 1 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)** can 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",
|
||||
"\n",
|
||||
"A function's **name** must be chosen according to the same naming rules as for ordinary variables. In fact, Python manages function names just 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 actually part of the name but must always be written out in the `def` statement for syntactic reasons.\n",
|
||||
"\n",
|
||||
|
|
@ -142,7 +142,7 @@
|
|||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"139655430681056"
|
||||
"140693945143776"
|
||||
]
|
||||
},
|
||||
"execution_count": 3,
|
||||
|
|
@ -289,7 +289,7 @@
|
|||
}
|
||||
},
|
||||
"source": [
|
||||
"Once defined we can **call** (i.e., \"execute\") a function with the **call operator** `()`. The formal parameters are filled in by passing variables or expressions as **arguments** to the function within the parentheses."
|
||||
"We can **call** (i.e., \"execute\") a function with the **call operator** `()` as often as we wish. The formal parameters are filled in by passing variables or expressions as **arguments** to the function within the parentheses."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -407,7 +407,7 @@
|
|||
}
|
||||
},
|
||||
"source": [
|
||||
"Notice how the parameters listed in a function's definition (i.e., `numbers`) and variables created inside it during execution (i.e., `evens` and `average`) are **local** to that function. That means they are only mapped to an object in memory while the function is being executed and de-referenced immediately when the function returns. We say they **go out of scope** once the function terminates."
|
||||
"Notice how the parameters listed in a function's definition (i.e., `numbers`) and variables created inside it during execution (i.e., `evens` and `average`) are **local** to that function. That means they only point to an object in memory *while* the function is being executed and de-referenced immediately when the function returns. We say they **go out of scope** once the function terminates."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -485,6 +485,17 @@
|
|||
"average"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "skip"
|
||||
}
|
||||
},
|
||||
"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` is 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)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
|
|
@ -504,7 +515,7 @@
|
|||
}
|
||||
},
|
||||
"source": [
|
||||
"On the contrary, while a function is being executed, it can \"see\" the variables of the **enclosing scope** (i.e., \"outside\" of it). This is a common source of *semantic* errors. Consider the following stylized (and incorrect) example `average_wrong()`. The error is hard to spot with eyes: The function never references the `numbers` parameter but the `nums` variable in the **global scope** instead."
|
||||
"On the contrary, while a function is being executed, it can reference the variables of **enclosing scopes** (i.e., \"outside\" of it). This is a common source of *semantic* errors. Consider the following stylized (and incorrect) example `average_wrong()`. The error is hard to spot with eyes: The function never references the `numbers` parameter but the `nums` variable in the **global scope** instead."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -526,11 +537,22 @@
|
|||
" Returns:\n",
|
||||
" float: average\n",
|
||||
" \"\"\"\n",
|
||||
" evens = [n for n in nums if n % 2 == 0] # should reference numbers, not nums\n",
|
||||
" evens = [n for n in nums if n % 2 == 0] # should reference numbers not nums\n",
|
||||
" average = sum(evens) / len(evens)\n",
|
||||
" return average"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "skip"
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
"`nums` in the global scope is of course unchanged."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
|
|
@ -555,6 +577,17 @@
|
|||
"nums"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "skip"
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
"Sometimes a function might return a correct solution for *some* inputs ..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
|
|
@ -576,7 +609,18 @@
|
|||
}
|
||||
],
|
||||
"source": [
|
||||
"average_wrong(nums) # the result is correct by accident!"
|
||||
"average_wrong(nums) # this is correct by accident"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "skip"
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
"... but still be wrong *in general*."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -611,7 +655,9 @@
|
|||
}
|
||||
},
|
||||
"source": [
|
||||
"Also, observe how both `average_evens()` and `average_wrong()` use the same names for their respective parameters and variables internally. For sure, Python is smart enough to not mix them up. This is because each function call creates a temporary **[namespace](https://en.wikipedia.org/wiki/Namespace)** that *isolates* the local scope's names for usage only from within the function. As we saw in the [Zen of Python](https://www.python.org/dev/peps/pep-0020/), \"namespaces are one honking great idea\" (cf., `import this`)."
|
||||
"[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",
|
||||
"\n",
|
||||
"The frames logic shown by PythonTutor is the mechanism by which Python not only manages the names inside one function call but for potentially many calls occuring simultaneously as we will see 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."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -633,9 +679,9 @@
|
|||
}
|
||||
},
|
||||
"source": [
|
||||
"Code gets even more confusing when variables by the same name from different scopes collide. In particular, what should we expect to happen if a function changes a globally defined variable internally?\n",
|
||||
"Code gets even more confusing when variables by the *same* name from *different* scopes collide. In particular, what should we expect to happen if a function \"changes\" a globally defined variable internally?\n",
|
||||
"\n",
|
||||
"`average_odds()` below works like `average_evens()` above except that it **[casts](https://en.wikipedia.org/wiki/Type_conversion)** (i.e., \"converts\") the elements of `numbers` as objects of type `int` with the [int()](https://docs.python.org/3/library/functions.html#int) built-in first before filtering and averaging them. In doing so, it introduces an *internal* variable `nums` whose name collides with the one in the global scope. The **inequality operator** `!=` is just the **reversed** version of `==`."
|
||||
"`average_odds()` below works like `average_evens()` above except that it **[casts](https://en.wikipedia.org/wiki/Type_conversion)** (i.e., \"converts\") the elements of `numbers` as objects of type `int` with the [int()](https://docs.python.org/3/library/functions.html#int) built-in first before filtering and averaging them. In doing so, it introduces an *internal* variable `nums` whose name collides with the one in the global scope. To filter for odd numbers, we use the **inequality operator** `!=` that is just the **reversed** version of `==`."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -652,7 +698,7 @@
|
|||
" \"\"\"Calculate the average of all odd numbers in a list.\n",
|
||||
"\n",
|
||||
" Args:\n",
|
||||
" numbers (list): a list of numbers; must be integers\n",
|
||||
" numbers (list): a list of numbers; must be integer-like\n",
|
||||
"\n",
|
||||
" Returns:\n",
|
||||
" float: average\n",
|
||||
|
|
@ -671,7 +717,7 @@
|
|||
}
|
||||
},
|
||||
"source": [
|
||||
"`nums` in the global scope is of course the same list from above."
|
||||
"`nums` in the global scope is still unchanged."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -706,7 +752,7 @@
|
|||
}
|
||||
},
|
||||
"source": [
|
||||
"As good practice, let's first use inputs for which we can calculate the answer in our heads to verify that `average_odds()` is correct."
|
||||
"As good practice, let's first use inputs for which we can calculate the answer in our heads to \"verify\" that `average_odds()` is \"correct\"."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -730,7 +776,7 @@
|
|||
}
|
||||
],
|
||||
"source": [
|
||||
"average_odds([1, 100, 3, 100, 5]) # verify the function's correctness with predictable inputs"
|
||||
"average_odds([1.0, 10.0, 3.0, 10.0, 5.0]) # verify correctness with predictable inputs"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -776,7 +822,7 @@
|
|||
}
|
||||
},
|
||||
"source": [
|
||||
"Python, however, is again smart enough to keep the two `nums` variables apart. So the global `nums` is still pointing to the very same list object as before."
|
||||
"Python, however, is again smart enough to keep all the involved `nums` variables apart. So the global `nums` is still pointing to the very same `list` object as before."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -811,7 +857,9 @@
|
|||
}
|
||||
},
|
||||
"source": [
|
||||
"The reason why everything works just fine 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 we save that for a later chapter.\n",
|
||||
"The reason why everything just 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",
|
||||
"\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",
|
||||
|
|
@ -1122,7 +1170,7 @@
|
|||
}
|
||||
},
|
||||
"source": [
|
||||
"So far we have only specified one parameter in each of our user-defined functions. In Chapter 1, however, we saw the built-in function [divmod()](https://docs.python.org/3/library/functions.html#divmod) taking two arguments. Obviously, the order of the numbers passed in mattered. Whenever we call a function and list its arguments in a comma seperated 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), however, we saw the built-in function [divmod()](https://docs.python.org/3/library/functions.html#divmod) taking two arguments. Obviously, the order of the numbers passed in mattered. Whenever we call a function and list its arguments in a comma seperated manner, we say that we pass in the arguments by position or refer to them as **positional arguments**."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -2036,7 +2084,7 @@
|
|||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"139655519584648"
|
||||
"140694050824664"
|
||||
]
|
||||
},
|
||||
"execution_count": 58,
|
||||
|
|
@ -2308,7 +2356,7 @@
|
|||
"source": [
|
||||
"Observe how the arguments passed to functions do not need to be just variables or simple literals. Instead, we can 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: An expression is *any* syntactically correct combination of variables and literals with operators. And the call operator `()` is just ... well another operator. So both of the next two code cells are just expressions! They have no permanent side effect in memory. We can 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): An expression is *any* syntactically correct combination of variables and literals with operators. And the call operator `()` is just ... well another operator. So both of the next two code cells are just expressions! They have no permanent side effect in memory. We can 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)."
|
||||
]
|
||||
|
|
@ -2649,7 +2697,7 @@
|
|||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"0.12717011866176486"
|
||||
"0.15268128055183228"
|
||||
]
|
||||
},
|
||||
"execution_count": 75,
|
||||
|
|
@ -2684,7 +2732,7 @@
|
|||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<bound method Random.choice of <random.Random object at 0x562161984ba8>>"
|
||||
"<bound method Random.choice of <random.Random object at 0x56111ba1cba8>>"
|
||||
]
|
||||
},
|
||||
"execution_count": 76,
|
||||
|
|
@ -2733,7 +2781,7 @@
|
|||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"8"
|
||||
"2"
|
||||
]
|
||||
},
|
||||
"execution_count": 78,
|
||||
|
|
@ -2881,7 +2929,7 @@
|
|||
"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 will look at it in depth in Chapter 9.\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, to execute terminal commands from within a Jupyter notebook, we just need to 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), to execute terminal commands from within a Jupyter notebook, we just need to 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, will just confirm that."
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue