Streamline previous content

This commit is contained in:
Alexander Hess 2019-11-06 11:10:29 +01:00
parent 974362288a
commit 8fd14a614d
6 changed files with 413 additions and 404 deletions

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",
@ -55,7 +55,7 @@
"\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](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Identity-/-\"Memory-Location\").\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#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": [
"139829511291360"
"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,7 +655,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_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#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\")"
]
},
{
@ -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,7 +1939,7 @@
}
},
"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 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": [
"139829616976344"
"139646755983752"
]
},
"execution_count": 58,
@ -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."
]
},
{
@ -2697,7 +2697,7 @@
{
"data": {
"text/plain": [
"0.4165845384207939"
"0.21932911318720072"
]
},
"execution_count": 75,
@ -2732,7 +2732,7 @@
{
"data": {
"text/plain": [
"<bound method Random.choice of <random.Random object at 0x56279067aaf8>>"
"<bound method Random.choice of <random.Random object at 0x55a6c6c03af8>>"
]
},
"execution_count": 76,
@ -2781,7 +2781,7 @@
{
"data": {
"text/plain": [
"2"
"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."
]
@ -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,

View file

@ -189,7 +189,7 @@
{
"data": {
"text/plain": [
"94731133531104"
"94711290794976"
]
},
"execution_count": 5,
@ -213,7 +213,7 @@
{
"data": {
"text/plain": [
"94731133531072"
"94711290794944"
]
},
"execution_count": 6,
@ -313,7 +313,7 @@
{
"data": {
"text/plain": [
"94731133518064"
"94711290781936"
]
},
"execution_count": 10,
@ -1505,19 +1505,19 @@
}
},
"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."
]
},
{
@ -1597,15 +1597,7 @@
"slide_type": "-"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"You read this just as often as you see heads when tossing a coin\n"
]
}
],
"outputs": [],
"source": [
"if random.random() > 0.5:\n",
" print(\"You read this just as often as you see heads when tossing a coin\")"
@ -1715,7 +1707,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"z is odd\n"
"z is positive\n"
]
}
],
@ -1753,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:"
]
@ -1847,7 +1839,7 @@
}
},
"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."
]
},
{
@ -1895,7 +1887,7 @@
}
},
"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."
]
},
{
@ -1935,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 construct in greater detail in [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb#List-Comprehensions)."
]
},
{
"cell_type": "markdown",
"metadata": {
@ -1967,9 +1948,9 @@
"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."
]
},
{
@ -2017,9 +1998,9 @@
}
},
"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`."
]
@ -2056,9 +2037,9 @@
}
},
"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."
]

View file

@ -859,7 +859,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"69.6 µs ± 25.8 µ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.55 ms ± 11.9 µ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": [
"189 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.07 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.41 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",
@ -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",
@ -4565,7 +4565,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"4.9 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
"4.69 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\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](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb#Mapping)."
"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=\" \")"
]
},
{
@ -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"
]
},
{
@ -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#Example:-Average-of-a-Subset-of-Numbers) 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",
@ -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

@ -21,7 +21,7 @@
"source": [
"After learning about the basic building blocks of expressing and structuring the business logic in programs, we focus our attention on the **data types** Python offers us, both built-in and available via the [standard library](https://docs.python.org/3/library/index.html) or third-party packages.\n",
"\n",
"We start with the \"simple\" ones: Numerical 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",
"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",
"[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",
@ -31,7 +31,7 @@
"- [Chapter 3](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals.ipynb#Boolean-Expressions) raises questions regarding the **limited precision** of `float` numbers (e.g., `42 == 42.000000000000001` evaluates to `True`), and\n",
"- [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb#Infinite-Recursion) shows that sometimes a `float` \"walks\" and \"quacks\" like an `int`, whereas the reverse is true in other cases.\n",
"\n",
"This chapter introduces all the [built-in numerical types](https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex): `int`, `float`, and `complex`. To mitigate the limited precision of floating-point numbers, we also look at two replacements for the `float` type in the [standard library](https://docs.python.org/3/library/index.html), namely the `Decimal` type in the [decimals](https://docs.python.org/3/library/decimal.html#decimal.Decimal) and the `Fraction` type in the [fractions](https://docs.python.org/3/library/fractions.html#fractions.Fraction) module."
"This chapter introduces all the [built-in numeric types](https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex): `int`, `float`, and `complex`. To mitigate the limited precision of floating-point numbers, we also look at two replacements for the `float` type in the [standard library](https://docs.python.org/3/library/index.html), namely the `Decimal` type in the [decimals](https://docs.python.org/3/library/decimal.html#decimal.Decimal) and the `Fraction` type in the [fractions](https://docs.python.org/3/library/fractions.html#fractions.Fraction) module."
]
},
{
@ -53,7 +53,7 @@
}
},
"source": [
"The simplest numerical type is the `int` type: It behaves like an [integer in ordinary math](https://en.wikipedia.org/wiki/Integer) (i.e., the set $\\mathbb{Z}$) and supports operators in the way we saw in the section on arithmetic operators in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#%28Arithmetic%29-Operators)."
"The simplest numeric type is the `int` type: It behaves like an [integer in ordinary math](https://en.wikipedia.org/wiki/Integer) (i.e., the set $\\mathbb{Z}$) and supports operators in the way we saw in the section on arithmetic operators in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#%28Arithmetic%29-Operators)."
]
},
{
@ -92,7 +92,7 @@
{
"data": {
"text/plain": [
"139630197982416"
"140087541220560"
]
},
"execution_count": 2,
@ -160,7 +160,7 @@
}
},
"source": [
"A nice feature is using underscores `_` as (thousands) separator in numerical literals. For example, `1_000_000` evaluates to `1000000` in memory."
"A nice feature is using underscores `_` as (thousands) separator in numeric literals. For example, `1_000_000` evaluates to `1000000` in memory."
]
},
{
@ -1165,7 +1165,7 @@
}
},
"source": [
"Whereas the boolean literals `True` and `False` are commonly *not* regarded as numerical types, they behave like `1` and `0` in an arithmetic context."
"Whereas the boolean literals `True` and `False` are commonly *not* regarded as numeric types, they behave like `1` and `0` in an arithmetic context."
]
},
{
@ -2115,7 +2115,7 @@
{
"data": {
"text/plain": [
"139630198164096"
"140087541402072"
]
},
"execution_count": 63,
@ -2218,7 +2218,7 @@
}
},
"source": [
"In cases where the dot `.` is unnecessary from a mathematical point of view, we either need to end the number with it nevertheless or use the [float()](https://docs.python.org/3/library/functions.html#float) built-in to cast the number explicitly. [float()](https://docs.python.org/3/library/functions.html#float) can process any numerical object or a properly formatted `str` object."
"In cases where the dot `.` is unnecessary from a mathematical point of view, we either need to end the number with it nevertheless or use the [float()](https://docs.python.org/3/library/functions.html#float) built-in to cast the number explicitly. [float()](https://docs.python.org/3/library/functions.html#float) can process any numeric object or a properly formatted `str` object."
]
},
{
@ -3517,7 +3517,7 @@
"source": [
"The [format()](https://docs.python.org/3/library/functions.html#format) function does *not* round a `float` object in the mathematical sense! It just allows us to show an arbitrary number of the digits as stored in memory, and it also does *not* change these.\n",
"\n",
"On the contrary, the built-in [round()](https://docs.python.org/3/library/functions.html#round) function creates a *new* numerical object that is a rounded version of the one passed in as the argument. It adheres to the common rules of math.\n",
"On the contrary, the built-in [round()](https://docs.python.org/3/library/functions.html#round) function creates a *new* numeric object that is a rounded version of the one passed in as the argument. It adheres to the common rules of math.\n",
"\n",
"For example, let's round `1 / 3` to five decimals. The obtained value for `roughly_a_third` is also *imprecise* but different from the \"exact\" representation of `1 / 3` above."
]
@ -4617,7 +4617,7 @@
}
},
"source": [
"The [quantize()](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) method allows us to [quantize](https://www.dictionary.com/browse/quantize) (i.e., \"round\") a `Decimal` number at any precision that is *smaller* than the set precision. It looks at the number of decimals (i.e., to the right of the period) of the numerical argument we pass in.\n",
"The [quantize()](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) method allows us to [quantize](https://www.dictionary.com/browse/quantize) (i.e., \"round\") a `Decimal` number at any precision that is *smaller* than the set precision. It looks at the number of decimals (i.e., to the right of the period) of the numeric argument we pass in.\n",
"\n",
"For example, as the overall imprecise value of `two` still has an internal precision of `28` digits, we can correctly round it to *four* decimals (i.e., `Decimal(\"0.0001\")` has four decimals)."
]
@ -5498,7 +5498,7 @@
{
"data": {
"text/plain": [
"139630197402224"
"140087540555856"
]
},
"execution_count": 176,
@ -5636,7 +5636,7 @@
}
},
"source": [
"Alternatively, we may use the [complex()](https://docs.python.org/3/library/functions.html#complex) built-in: This takes two parameters where the second is optional and defaults to `0`. We may either call it with one or two arguments of any numerical type or a `str` object in the format of the previous code cell without any spaces."
"Alternatively, we may use the [complex()](https://docs.python.org/3/library/functions.html#complex) built-in: This takes two parameters where the second is optional and defaults to `0`. We may either call it with one or two arguments of any numeric type or a `str` object in the format of the previous code cell without any spaces."
]
},
{
@ -5708,7 +5708,7 @@
}
],
"source": [
"complex(Decimal(\"2.0\"), Fraction(1, 2)) # the arguments may be any numerical type"
"complex(Decimal(\"2.0\"), Fraction(1, 2)) # the arguments may be any numeric type"
]
},
{
@ -5743,7 +5743,7 @@
}
},
"source": [
"Arithmetic expressions work with `complex` numbers. They may be mixed with the other numerical types, and the result is always a `complex` number."
"Arithmetic expressions work with `complex` numbers. They may be mixed with the other numeric types, and the result is always a `complex` number."
]
},
{
@ -6111,7 +6111,7 @@
}
},
"source": [
"Analogous to the discussion of containers and iterables in [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb#Containers-vs.-Iterables), we contrast the *concrete* numerical data types in this chapter with the *abstract* ideas behind [numbers in mathematics](https://en.wikipedia.org/wiki/Number).\n",
"Analogous to the discussion of containers and iterables in [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb#Containers-vs.-Iterables), we contrast the *concrete* numeric data types in this chapter with the *abstract* ideas behind [numbers in mathematics](https://en.wikipedia.org/wiki/Number).\n",
"\n",
"The figure below summarizes five *major* sets of [numbers in mathematics](https://en.wikipedia.org/wiki/Number) as we know them from high school:\n",
"\n",
@ -6723,7 +6723,7 @@
}
},
"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* numerical type implementing the *abstract* `numbers.Real` type supports it (cf., [documentation](https://docs.python.org/3/library/numbers.html#numbers.Real)).\n",
"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)."
]
@ -7015,7 +7015,7 @@
}
},
"source": [
"There exist three numerical types in core Python:\n",
"There exist three numeric types in core Python:\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",

View file

@ -19,7 +19,7 @@
}
},
"source": [
"In this chapter, we continue the study of the built-in data types. Building on our knowledge of numbers, the next layer consists of textual data that are modeled primarily with the `str` type in Python. `str` objects are naturally more \"complex\" than numerical objects as any text consists of an arbitrary and possibly large number of individual characters that may be chosen from any alphabet in the history of humankind. Luckily, Python abstracts away most of this complexity."
"In this chapter, we continue the study of the built-in data types. Building on our knowledge of numbers, the next layer consists of textual data that are modeled primarily with the `str` type in Python. `str` objects are naturally more \"complex\" than numeric objects as any text consists of an arbitrary and possibly large number of individual characters that may be chosen from any alphabet in the history of humankind. Luckily, Python abstracts away most of this complexity."
]
},
{
@ -80,7 +80,7 @@
{
"data": {
"text/plain": [
"140133793916176"
"140488988107952"
]
},
"execution_count": 2,
@ -737,14 +737,14 @@
}
},
"source": [
"The idea of a **sequence** is yet another *abstract* concept.\n",
"A **sequence** is yet another *abstract* concept (cf., *containers* and *iterables* in [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb#Containers-vs.-Iterables)).\n",
"\n",
"It unifies *four* [orthogonal](https://en.wikipedia.org/wiki/Orthogonality) (i.e., \"independent\") *abstract* concepts into one: Any *concrete* data type, such as `str`, is considered a sequence if it simultaneously\n",
"It unifies *four* [orthogonal](https://en.wikipedia.org/wiki/Orthogonality) (i.e., \"independent\") behaviors into one idea: Any data type, such as `str`, is considered a sequence if it simultaneously\n",
"\n",
"1. **contains** other \"things,\"\n",
"2. is **iterable**, and \n",
"3. comes with a *predictable* **order** of its\n",
"4. **finite** number of elements.\n",
"4. **finite** number of \"things.\"\n",
"\n",
"[Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb#Collections-vs.-Sequences) formalizes sequences in great detail. Here, we keep our focus on the `str` type that historically received its name as it models a \"**[string of characters](https://en.wikipedia.org/wiki/String_%28computer_science%29)**,\" and a \"string\" is more formally called a sequence in the computer science literature.\n",
"\n",
@ -1441,7 +1441,7 @@
}
},
"source": [
"Whereas elements of a `list` object *may* be *re-assigned*, as shortly hinted at in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Who-am-I?-And-how-many?), this is *not* allowed for `str` objects. Once created, they *cannot* be *changed*. Formally, we say that they are **immutable**. In that regard, `str` objects and all the numerical types in [Chapter 5](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers.ipynb) are alike.\n",
"Whereas elements of a `list` object *may* be *re-assigned*, as shortly hinted at in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Who-am-I?-And-how-many?), this is *not* allowed for `str` objects. Once created, they *cannot* be *changed*. Formally, we say that they are **immutable**. In that regard, `str` objects and all the numeric types in [Chapter 5](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers.ipynb) are alike.\n",
"\n",
"On the contrary, objects that may be changed after creation, are called **mutable**. We already saw in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Who-am-I?-And-how-many?) how mutable objects are more difficult to reason about for a beginner, in particular, if more than *one* variable point to one. Yet, mutability does have its place in a programmer's toolbox, and we revisit this idea in the next chapters.\n",
"\n",
@ -1533,7 +1533,7 @@
{
"data": {
"text/plain": [
"140133784610832"
"140488987187120"
]
},
"execution_count": 46,
@ -1557,7 +1557,7 @@
{
"data": {
"text/plain": [
"140133793916176"
"140488988107952"
]
},
"execution_count": 47,
@ -1972,7 +1972,7 @@
{
"data": {
"text/plain": [
"140133891261864"
"140489068668384"
]
},
"execution_count": 61,
@ -2009,7 +2009,7 @@
{
"data": {
"text/plain": [
"140133784910848"
"140488987340848"
]
},
"execution_count": 63,