Streamline previous content

This commit is contained in:
Alexander Hess 2019-10-07 22:31:06 +02:00
parent 07e5460506
commit 3b28b8d507
6 changed files with 493 additions and 496 deletions

View file

@ -224,7 +224,7 @@
}
},
"source": [
"For more \"couragous\" beginners wanting to learn how to accomplish this, here is a rough sketch of the aspects to know. First, all Python realeases are available for free on the official [download](https://www.python.org/downloads/) page for any supported operating system. Choose the one you want, then download and install it (cf., the [instruction notes](https://wiki.python.org/moin/BeginnersGuide/Download)). As this only includes core Python and the standard library, the beginner then needs to learn about the [pip](https://pip.pypa.io/en/stable/) module, which is to be used inside a terminal. With the command `python -m pip install jupyter`, all necessary third-party libraries can be installed (cf., more background [here](https://jupyter.readthedocs.io/en/latest/install.html)). However, this would be done in a *system-wide* fashion and is not recommended. Instead, the best practice is to create a so-called **virtual environment** with the [venv](https://docs.python.org/3/library/venv.html) module with which the installed third-party packages can be *isolated* on a per-project basis (the command `python -m venv env-name` creates a virtual enviroment called \"env-name\"). This tactic is employed to avoid a situation known as **[dependency hell](https://en.wikipedia.org/wiki/Dependency_hell)** Once created, the virtual environment must then be activated each time before resuming work in each terminal (with the command `source env-name/bin/activate`). While there exist convenience tools that automate parts of this (e.g., [poetry](https://poetry.eustace.io/docs/) or [virtualenvwrapper](https://virtualenvwrapper.readthedocs.io/en/latest/)), it only distracts a beginner from actually studying the Python language. Yet, it is still worthwhile to have heard about these terms and concepts as many online resources often implicitly assume the user to know about them."
"For more \"couragous\" beginners wanting to learn how to accomplish this, here is a rough sketch of the aspects to know. First, all Python realeases are available for free on the official [download](https://www.python.org/downloads/) page for any supported operating system. Choose the one you want, then download and install it (cf., the [instruction notes](https://wiki.python.org/moin/BeginnersGuide/Download)). As this only includes core Python and the standard library, the beginner then needs to learn about the [pip](https://pip.pypa.io/en/stable/) module, which is to be used inside a terminal. With the command `python -m pip install jupyter`, all necessary third-party libraries can be installed (cf., more background [here](https://jupyter.readthedocs.io/en/latest/install.html)). However, this would be done in a *system-wide* fashion and is not recommended. Instead, the best practice is to create a so-called **virtual environment** with the [venv](https://docs.python.org/3/library/venv.html) module with which the installed third-party packages are *isolated* on a per-project basis (the command `python -m venv env-name` creates a virtual enviroment called \"env-name\"). This tactic is employed to avoid a situation known as **[dependency hell](https://en.wikipedia.org/wiki/Dependency_hell)** Once created, the virtual environment must then be activated each time before resuming work in each terminal (with the command `source env-name/bin/activate`). While there exist convenience tools that automate parts of this (e.g., [poetry](https://poetry.eustace.io/docs/) or [virtualenvwrapper](https://virtualenvwrapper.readthedocs.io/en/latest/)), it only distracts a beginner from actually studying the Python language. Yet, it is still worthwhile to have heard about these terms and concepts as many online resources often implicitly assume the user to know about them."
]
},
{
@ -778,11 +778,11 @@
"**Part 1: Expressing Logic**\n",
"\n",
"- What is a programming language? What kind of words exist?\n",
" 1. Elements of a Program\n",
" 2. Functions & Modularization\n",
" 1. [Elements of a Program](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb)\n",
" 2. [Functions & Modularization](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb)\n",
"- What is the flow of execution? How can we form sentences from words?\n",
" 3. Conditionals & Exceptions\n",
" 4. Recursion & Looping"
" 3. [Conditionals & Exceptions](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals.ipynb)\n",
" 4. [Recursion & Looping](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb)"
]
},
{
@ -796,7 +796,7 @@
"**Part 2: Managing Data and Memory**\n",
"\n",
"- How is data stored in memory?\n",
" 5. Numbers\n",
" 5. [Numbers](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers.ipynb)\n",
" 6. Text\n",
" 7. Sequences\n",
" 8. Mappings & Sets\n",

File diff suppressed because it is too large Load diff

View file

@ -23,7 +23,7 @@
"\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",
"This chapter shows how Python offers language constructs that let us **define** our own functions that we can then **call** just like the built-in ones."
"This chapter shows how Python offers language constructs that let us **define** our own functions that we may then **call** just like the built-in ones."
]
},
{
@ -45,11 +45,11 @@
}
},
"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](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) in its final Pythonic version and transform it into the function `average_evens()` below. \n",
"So-called **[user-defined functions](https://docs.python.org/3/reference/compound_stmts.html#function-definitions)** may be created with the `def` statement. To extend an already familiar example, we re-use the introductory example from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) 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",
"Functions may define an arbitrary number of **parameters** as inputs that can then be referenced within the indented **code block**: They are simply listed within the parentheses in the `def` statement (i.e., `numbers` below). \n",
"Functions may define an arbitrary number of **parameters** as inputs that are then referenced within the indented **code block**: They are simply listed within the parentheses in the `def` statement (i.e., `numbers` below). \n",
"\n",
"The code block is often also called a function's **body** while the first line with the `def` in it is the **header** and must end with a colon.\n",
"\n",
@ -92,7 +92,7 @@
}
},
"source": [
"Once defined, a function can be referenced just like any other variable by its name (i.e., *without* the parenthesis). Its value might seem awkward at first: It consists of the location where we defined the function (i.e., `__main__`, which is Python's way of saying \"in this notebook\") and the signature."
"Once defined, a function may be referenced just like any other variable by its name (i.e., *without* the parenthesis). Its value might seem awkward at first: It consists of the location where we defined the function (i.e., `__main__`, which is Python's way of saying \"in this notebook\") and the signature."
]
},
{
@ -142,7 +142,7 @@
{
"data": {
"text/plain": [
"140693945143776"
"139925407773152"
]
},
"execution_count": 3,
@ -289,7 +289,7 @@
}
},
"source": [
"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."
"We **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."
]
},
{
@ -863,7 +863,7 @@
"\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",
"While this is not a problem for Python as we have observed, it may lead to less readable code for us humans and should be avoided if possible. But, as we have also heard, \"[naming things](https://skeptics.stackexchange.com/questions/19836/has-phil-karlton-ever-said-there-are-only-two-hard-things-in-computer-science)\" is often considered hard as well and we have to be prepared to encounter shadowing variables."
"While this is not a problem for Python, it may lead to less readable code for us humans and should be avoided if possible. But, as we have also heard, \"[naming things](https://skeptics.stackexchange.com/questions/19836/has-phil-karlton-ever-said-there-are-only-two-hard-things-in-computer-science)\" is often considered hard as well and we have to be prepared to encounter shadowing variables."
]
},
{
@ -946,7 +946,7 @@
}
},
"source": [
"We can cast certain 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. This actually creates a *new* object of type `int` from the provided `avg` or `\"6\"` objects who continue to exist in memory unchanged."
"We may cast certain 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. This actually creates a *new* object of type `int` from the provided `avg` or `\"6\"` objects who continue to exist in memory unchanged."
]
},
{
@ -1088,7 +1088,7 @@
}
},
"source": [
"Not all conversions are valid and *runtime* errors can occur as the `ValueError` shows."
"Not all conversions are valid and *runtime* errors may occur as the `ValueError` shows."
]
},
{
@ -1124,7 +1124,7 @@
}
},
"source": [
"We can also go in the other direction with the [float()](https://docs.python.org/3/library/functions.html#float) built-in function."
"We may also go in the other direction with the [float()](https://docs.python.org/3/library/functions.html#float) built-in function."
]
},
{
@ -1266,7 +1266,7 @@
}
},
"source": [
"As with [divmod()](https://docs.python.org/3/library/functions.html#divmod), we can pass in the arguments by position."
"As with [divmod()](https://docs.python.org/3/library/functions.html#divmod), we pass in the arguments by position."
]
},
{
@ -1303,7 +1303,7 @@
"source": [
"However, now the function call is a bit harder to comprehend as we need to always remember what the `2` means. This becomes even harder the more parameters we specify.\n",
"\n",
"Luckily, we can also reference the formal parameter names as **keyword arguments**. We can even combine positional and keyword arguments in the same function call. Each of the following does the exact same thing."
"Luckily, we may also reference the formal parameter names as **keyword arguments**. We can even combine positional and keyword arguments in the same function call. Each of the following does the exact same thing."
]
},
{
@ -1555,9 +1555,9 @@
}
},
"source": [
"Now we can 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 passed in, this can be done as either a positional or a keyword argument. Which of the two versions where `scalar` is `2` is easier to comprehend in a large program?"
"If `scalar` is passed in, this may be done as either a positional or a keyword argument. Which of the two versions where `scalar` is `2` is easier to comprehend in a large program?"
]
},
{
@ -1869,7 +1869,7 @@
}
},
"source": [
"Now we can call `add_three()` as if we defined it with the `def` statement to begin with."
"Now we call `add_three()` as if we defined it with the `def` statement to begin with."
]
},
{
@ -2084,7 +2084,7 @@
{
"data": {
"text/plain": [
"140694050824664"
"139925472068136"
]
},
"execution_count": 58,
@ -2132,7 +2132,7 @@
"\n",
"Let's see what we can do with the `math` module.\n",
"\n",
"The [dir()](https://docs.python.org/3/library/functions.html#dir) built-in function can also be used with an argument passed in. Ignoring the dunder-style names, `math` offers quite a lot of ... names. As we cannot know at this point in time if a listed name refers to a function or an ordinary variable, we use the more generic term **attribute** to mean either one of them."
"The [dir()](https://docs.python.org/3/library/functions.html#dir) built-in function may also be used with an argument passed in. Ignoring the dunder-style names, `math` offers quite a lot of ... names. As we cannot know at this point in time if a listed name refers to a function or an ordinary variable, we use the more generic term **attribute** to mean either one of them."
]
},
{
@ -2354,9 +2354,9 @@
}
},
"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",
"Observe how the arguments passed to functions do not need to be just variables or simple literals. Instead, we may pass in any *expression* that evaluates to a *new* object of the type the function expects.\n",
"\n",
"So just as a reminder from the expression vs. statement discussion in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb): An expression is *any* syntactically correct combination of variables and literals with operators. And the call operator `()` is 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 may execute them as often as we want *without* changing the state of the program (i.e., this Jupyter notebook).\n",
"\n",
"So, regarding the very next cell in particular: Although the `2 ** 2` creates a *new* object `4` in memory that is then immediately passed into the [math.sqrt()](https://docs.python.org/3/library/math.html#math.sqrt) function, once that function call returns, \"all is lost\" and the newly created `4` object is forgotten again, as well as the return value of [math.sqrt()](https://docs.python.org/3/library/math.html#math.sqrt)."
]
@ -2428,7 +2428,7 @@
}
},
"source": [
"If we only need one particular function from a module, we can also use the alternative `from ... import ...` syntax.\n",
"If we only need one particular function from a module, we may also use the alternative `from ... import ...` syntax.\n",
"\n",
"This does *not* create a module object but only makes a variable in our current location point to an object defined inside a module directly."
]
@ -2537,7 +2537,7 @@
}
},
"source": [
"Besides the usual dunder-style attributes, the [dir()](https://docs.python.org/3/library/functions.html#dir) built-in function lists some attributes in an upper case naming convention and many others starting with a single underscore \"\\_\". To understand the former, we have to wait until Chapter 10 while the latter are explained further below."
"Besides the usual dunder-style attributes, the [dir()](https://docs.python.org/3/library/functions.html#dir) built-in function lists some attributes in an upper case naming convention and many others starting with a single underscore `_`. To understand the former, we have to wait until Chapter 10 while the latter are explained further below."
]
},
{
@ -2697,7 +2697,7 @@
{
"data": {
"text/plain": [
"0.15268128055183228"
"0.270353921677863"
]
},
"execution_count": 75,
@ -2732,7 +2732,7 @@
{
"data": {
"text/plain": [
"<bound method Random.choice of <random.Random object at 0x56111ba1cba8>>"
"<bound method Random.choice of <random.Random object at 0x56063d20aba8>>"
]
},
"execution_count": 76,
@ -2781,7 +2781,7 @@
{
"data": {
"text/plain": [
"2"
"7"
]
},
"execution_count": 78,
@ -2801,7 +2801,7 @@
}
},
"source": [
"In order to re-produce the same random numbers in a simulation each time we run it, we can 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 will get the exact same random numbers again. This becomes very important, for example, when we employ certain machine learning algorithms that rely on randomization, like the infamous [Random Forest](https://en.wikipedia.org/wiki/Random_forest), and want to obtain **re-producable** results.\n",
"In order to re-produce 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 will get the exact same random numbers again. This becomes very important, for example, when we employ certain machine learning algorithms that rely on randomization, like the infamous [Random Forest](https://en.wikipedia.org/wiki/Random_forest), and want to obtain **re-producable** results.\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."
]
@ -2899,7 +2899,7 @@
}
},
"source": [
"As the Python community is based around open source, many developers publish their code, for example, on the Python Package Index [PyPI](https://pypi.org) from where anyone can download and install it for free using command line based tools like [pip](https://pip.pypa.io/en/stable/) or [conda](https://conda.io/en/latest/). This way, we can always customize our Python installation even more. Managing many such packages is actually quite a deep topic on its own, sometimes fearfully called **[dependency hell](https://en.wikipedia.org/wiki/Dependency_hell)**.\n",
"As the Python community is based around open source, many developers publish their code, for example, on the Python Package Index [PyPI](https://pypi.org) from where anyone may download and install it for free using command line based tools like [pip](https://pip.pypa.io/en/stable/) or [conda](https://conda.io/en/latest/). This way, we can always customize our Python installation even more. Managing many such packages is actually quite a deep topic on its own, sometimes fearfully called **[dependency hell](https://en.wikipedia.org/wiki/Dependency_hell)**.\n",
"\n",
"The difference between the [standard library](https://docs.python.org/3/library/index.html) and such **third-party** packages is that in the first case the code goes through a much more formalized review process and is officially endorsed by the Python core developers. Yet, many third-party projects also offer the highest quality standards and a lot of such software is actually also relied on by many businesses and researchers.\n",
"\n",
@ -2987,7 +2987,7 @@
}
},
"source": [
"`np` can be used in the same way as `math` or `random` above."
"`np` is used in the same way as `math` or `random` above."
]
},
{
@ -3094,7 +3094,7 @@
}
},
"source": [
"[numpy](http://www.numpy.org/) somehow magically adds new behavior to Python's built-in arithmetic operators. For example, we can now [scalar-multiply](https://en.wikipedia.org/wiki/Scalar_multiplication) `vec`.\n",
"[numpy](http://www.numpy.org/) somehow magically adds new behavior to Python's built-in arithmetic operators. For example, we may now [scalar-multiply](https://en.wikipedia.org/wiki/Scalar_multiplication) `vec`.\n",
"\n",
"[numpy](http://www.numpy.org/)'s functions are implemented in highly optimized C code and therefore fast, especially when it comes to big data."
]
@ -3292,7 +3292,7 @@
"source": [
"Disregarding the dunder-style attributes, `mod` defines the five attributes `_default_scalar`, `_scaled_average`, `average`, `average_evens`, and `average_odds`, which are exactly the ones we would expect from reading the [*sample_module.py*](https://github.com/webartifex/intro-to-python/blob/master/sample_module.py) file.\n",
"\n",
"An important convention when working with imported code is to *disregard* any attributes starting with an underscore \"\\_\". These are considered **private** and constitute **implementation details** the author of the imported code might change in a future version of his software. We *must* not rely on them in any way.\n",
"An important convention when working with imported code is to *disregard* any attributes starting with an underscore `_`. These are considered **private** and constitute **implementation details** the author of the imported code might change in a future version of his software. We *must* not rely on them in any way.\n",
"\n",
"In contrast, the three remaining **public** attributes are the functions `average()`, `average_evens()`, and `average_odds()` that we may use after the import."
]
@ -3341,7 +3341,7 @@
}
},
"source": [
"We can use the imported `mod.average_evens()` just like `average_evens()` defined above. The advantage we get from **modularization** with *.py* files is that we can now easily re-use functions across different Jupyter notebooks without re-defining them again and again. Also, we can \"source out\" code that distracts from the storyline told in a notebook."
"We use the imported `mod.average_evens()` just like `average_evens()` defined above. The advantage we get from **modularization** with *.py* files is that we can now easily re-use functions across different Jupyter notebooks without re-defining them again and again. Also, we can \"source out\" code that distracts from the storyline told in a notebook."
]
},
{
@ -3433,7 +3433,7 @@
}
},
"source": [
"Packages are a generalization of modules and we will look at one in Chapter 10 in detail. You can, however, already look at a [sample package](https://github.com/webartifex/intro-to-python/tree/master/sample_package) in the repository, which is nothing but a folder with *.py* files in it.\n",
"Packages are a generalization of modules and we will look at one in Chapter 10 in detail. You may, however, already look at a [sample package](https://github.com/webartifex/intro-to-python/tree/master/sample_package) in the repository, which is nothing but a folder with *.py* files in it.\n",
"\n",
"As a further references on modules, we refer to the [official tutorial](https://docs.python.org/3/tutorial/modules.html)."
]
@ -3464,11 +3464,11 @@
"- make programs easier to comprehend and debug for humans as they give names to the smaller parts of a larger program (i.e., they **modularize** a code base), and\n",
"- eliminate redundancies by allowing **re-use of code**.\n",
"\n",
"Functions are **defined** once with the `def` statement. Then, they can be **called** many times with the call operator `()`.\n",
"Functions are **defined** once with the `def` statement. Then, they may be **called** many times with the call operator `()`.\n",
"\n",
"They may process **parameterized** inputs, **passed** in as **arguments**, and output a **return value**.\n",
"\n",
"Arguments can be passed in by **position** or **keyword**. Some functions may even require **keyword-only** arguments.\n",
"Arguments may be passed in by **position** or **keyword**. Some functions may even require **keyword-only** arguments.\n",
"\n",
"**Lambda expressions** create anonymous functions.\n",
"\n",

View file

@ -19,9 +19,9 @@
}
},
"source": [
"We analyzed every aspect of the `average_evens()` function in [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb) except for the `if` part. While it seems to intuitively do what we expect it to, there is a whole lot more to be learned from taking it apart. In particular, the `if` can occur within both a **statement** as in our introductory example in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) but also an **expression** as in `average_evens()`. This is analogous as to how a noun in a natural language is *either* the subject of *or* an object in a sentence. What is common to both versions of the `if` is that it leads to code being executed for *parts* of the input only. It is our first way of **controlling** the **flow of execution** in a program.\n",
"We analyzed every aspect of the `average_evens()` function in [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb) except for the `if` related parts. While it seems to intuitively do what we expect it to, there is a whole lot more to be learned from taking it apart. In particular, the `if` may occur within both a **statement** as in our introductory example in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) but also an **expression** as in `average_evens()`. This is analogous as to how a noun in a natural language is *either* the subject of *or* an object in a sentence. What is common to both versions of the `if` is that it leads to code being executed for *parts* of the input only. It is our first way of **controlling** the **flow of execution** in a program.\n",
"\n",
"After deconstructing `if` in the first part of this chapter, we take a close look at a similar concept, namely handling and raising **exceptions**."
"After deconstructing `if` in the first part of this chapter, we take a close look at a similar concept, namely handling **exceptions**."
]
},
{
@ -104,7 +104,7 @@
}
},
"source": [
"Observe how `==` can handle objects of *different* type. This shows how it implements a notion of equality in line with how we humans think of things being equal or not. After all, `42` and `42.0` are totally different $0$s and $1$s for a computer and many programming languages would actually say `False` here! Technically, this is yet another example of operator overloading."
"The `==` operator handles objects of *different* type. This shows how it implements a notion of equality in line with how we humans think of things being equal or not. After all, `42` and `42.0` are totally different $0$s and $1$s for a computer and many programming languages would actually say `False` here! Technically, this is yet another example of operator overloading."
]
},
{
@ -139,7 +139,7 @@
}
},
"source": [
"There are, however, cases where even well-behaved Python does not make us happy. Chapter 5 will provide more insights on this \"bug\"."
"There are, however, cases where even well-behaved Python does not make us happy. [Chapter 5](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers.ipynb) will provide more insights on this \"bug\"."
]
},
{
@ -189,7 +189,7 @@
{
"data": {
"text/plain": [
"94163040564192"
"94697002906592"
]
},
"execution_count": 5,
@ -213,7 +213,7 @@
{
"data": {
"text/plain": [
"94163040564160"
"94697002906560"
]
},
"execution_count": 6,
@ -313,7 +313,7 @@
{
"data": {
"text/plain": [
"94163040551152"
"94697002893552"
]
},
"execution_count": 10,
@ -359,7 +359,7 @@
"source": [
"`True`, `False`, and `None` have the property that they each exist in memory only *once*. Objects designed this way are so-called **singletons**. This **[design pattern](https://en.wikipedia.org/wiki/Design_Patterns)** was originally developed to keep a program's memory usage at a minimum. It may only be employed in situations where we know that an object will *not* mutate its value (i.e., to re-use the bag analogy from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb), no flipping of $0$s and $1$s in the bag is allowed). In languages \"closer\" to the memory like C we would have to code this singleton logic ourselves but Python has this already built in for *some* types.\n",
"\n",
"We can verify this with either the `is` operator or by comparing memory addresses."
"We verify this with either the `is` operator or by comparing memory addresses."
]
},
{
@ -523,7 +523,7 @@
}
},
"source": [
"The \"less than\" `<` or \"greater than\" `>` operators on their own mean \"strictly less than\" or \"strictly greater than\" but can be combined with the equality operator into just `<=` and `>=`. This is a shortcut for using the logical `or` operator as described in the next section."
"The \"less than\" `<` or \"greater than\" `>` operators on their own mean \"strictly less than\" or \"strictly greater than\" but may be combined with the equality operator into just `<=` and `>=`. This is a shortcut for using the logical `or` operator as described in the next section."
]
},
{
@ -641,7 +641,7 @@
}
},
"source": [
"Boolean expressions can be combined or negated with the **logical operators** `and`, `or`, and `not` to form new boolean expressions. Of course, this may be done *recursively* as well to obtain boolean expressions of arbitrary complexity.\n",
"Boolean expressions may be combined or negated with the **logical operators** `and`, `or`, and `not` to form new boolean expressions. Of course, this may be done *recursively* as well to obtain boolean expressions of arbitrary complexity.\n",
"\n",
"Their usage is similar to how the equivalent words are used in plain English:\n",
"\n",
@ -1133,7 +1133,7 @@
}
},
"source": [
"## Conditional Statements"
"## The `if` Statement"
]
},
{
@ -1146,13 +1146,13 @@
"source": [
"In order 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",
"\n",
"One major language construct to do so is the **[conditional statement](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement)** or `if` statement. It consists of:\n",
"One major 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",
"\n",
"- *one* mandatory `if`-clause,\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 a \"catch everything else\" case.\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",
"\n",
"In terms of syntax, the header lines end with a colon and the code blocks are indented.\n",
"\n",
@ -1236,15 +1236,7 @@
"slide_type": "-"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"You will read this just as often as you see heads when tossing a coin\n"
]
}
],
"outputs": [],
"source": [
"if random.random() > 0.5:\n",
" print(\"You will read this just as often as you see heads when tossing a coin\")"
@ -1258,7 +1250,7 @@
}
},
"source": [
"More often than not, we might model a binary choice."
"More often than not, we model a **binary choice**."
]
},
{
@ -1293,7 +1285,9 @@
}
},
"source": [
"We may **nest** `if` statements to control the flow of execution in a more granular way. Every additional layer, however, makes the code less readable, in particular, if we have more than one line per code block."
"We may **nest** `if` statements to control the flow of execution in a more granular way. Every additional layer, however, makes the code *less* readable, in particular, if we have more than one line per code block.\n",
"\n",
"The code cell below *either* checks if a number is even or odd *or* if it is positive or negative."
]
},
{
@ -1334,7 +1328,9 @@
}
},
"source": [
"A good way to make this code more readable is to introduce **temporary variables** *in combination* with using the `and` operator to **flatten** the branching logic. The `if` statement then reads almost like plain English. In contrast to many other languages, creating variables is a computationally *cheap* operation in Python and also helps to document the code *inline*. Without temporary variables, the `and` flattening could actually lead to more sub-expressions in the conditions be evaluated than necessary. Do you see why?"
"A good way to make this code more readable is to introduce **temporary variables** *in combination* with the `and` operator to **flatten** the branching logic. The `if` statement then reads almost like plain English. In contrast to many other languages, creating variables is a computationally *cheap* operation in Python and also helps to document the code *inline* with meaningful variable names.\n",
"\n",
"Flattening the logic *without* temporary variables could actually lead to more sub-expressions in the conditions be evaluated than necessary. Do you see why?"
]
},
{
@ -1377,7 +1373,7 @@
}
},
"source": [
"## Conditional Expressions"
"## The `if` Expression"
]
},
{
@ -1388,7 +1384,7 @@
}
},
"source": [
"When all we do with an `if` statement is to assign an object to a variable with respect to a single true-or-false condition (cf., binary choice above), there is a shortcut for that: We could simply assign the result of a so-called **conditional expression** or `if` expression to the variable.\n",
"When all we do with an `if` statement is to assign an object to a variable with respect to a single true-or-false condition (i.e., a binary choice), there is a shortcut for that: We could simply assign the result of a so-called **conditional expression**, or `if` expression for short, to the variable.\n",
"\n",
"Think of a situation where we evaluate a piece-wise functional relationship $y = f(x)$ at a given $x$, for example:"
]
@ -1530,7 +1526,7 @@
}
},
"source": [
"In this concrete 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 would be to use the built-in [max()](https://docs.python.org/3/library/functions.html#max) function."
]
},
{
@ -1578,7 +1574,7 @@
}
},
"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 that is introduced in Chapter 7."
"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)."
]
},
{
@ -1589,7 +1585,7 @@
}
},
"source": [
"## Exceptions"
"## The `try` Statement"
]
},
{
@ -1600,7 +1596,7 @@
}
},
"source": [
"In the previous two chapters we already 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 occurence of such exceptions. All we need for that is a way to formulate a condition for that.\n",
"In the previous two chapters we already 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 occurence 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 its own language construct for it, namely the `try` [statement](https://docs.python.org/3/reference/compound_stmts.html#the-try-statement).\n",
"\n",
@ -1652,9 +1648,9 @@
}
},
"source": [
"However, it is good practise to *not* **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 here that the code base becomes easier to understand as we clearly communicate to any human reader what could go wrong during execution. 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 to *not* **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 here that the code base becomes easier to understand as we clearly communicate to any human reader what could go wrong during execution. 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 practise is to always keep the code in the `try`-branch short so as to not accidently handle an exception we do not want to handle.\n",
"Another good practice is to always keep the code in the `try`-branch short so as to not accidently handle an exception we do not want to handle.\n",
"\n",
"In the example, we are dividing numbers and may therefore expect a `ZeroDivisionError`."
]
@ -1691,9 +1687,9 @@
}
},
"source": [
"Often, we must have some code run independent of an exception occuring (e.g., to close a connection to a database). To achieve that, we can add an optional `finally`-branch to the `try` statement.\n",
"Often, we may have to run some code *independent* of an exception occuring, for example, to close a connection to a database. To achieve that, we add a `finally`-branch to the `try` statement.\n",
"\n",
"Similarly, we might have some code that must be run exactly when no exception occurs but we do not want to put it in the `try`-branch as per the good practice mentioned. To achieve that, we can add an optional `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`-branch as per the good practice mentioned above. To achieve that, we add an `else`-branch 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. It's actually quite easy."
]
@ -1711,7 +1707,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"Oops. Division by 0. How does that work?\n",
"Yes, division worked smoothly.\n",
"I am always printed\n"
]
}

View file

@ -197,9 +197,9 @@
}
},
"source": [
"Whenever we can find a recursive way of formulating an idea, we can immediately translate it into Python in a *naive* way (i.e., we create a *correct* program that may *not* be an *efficient* implementation yet).\n",
"Whenever we find a recursive way of formulating an idea, we can immediately translate it into Python in a *naive* way (i.e., we create a *correct* program that may *not* be an *efficient* implementation yet).\n",
"\n",
"Below is a first version of `factorial()`: The `return` statement does not have to be a function's last code line and we can certainly have several `return` statements as well."
"Below is a first version of `factorial()`: The `return` statement does not have to be a function's last code line and we may certainly have several `return` statements as well."
]
},
{
@ -554,7 +554,7 @@
}
},
"source": [
"Euclid's algorithm is stunningly fast, even for large numbers. Its speed comes from the use of the modulo operation `%`. However, this is *not* true for recusion in general, which can result in very slow programs if not applied correctly."
"Euclid's algorithm is stunningly fast, even for large numbers. Its speed comes from the use of the modulo operator `%`. However, this is *not* true for recusion in general, which may result in very slow programs if not applied correctly."
]
},
{
@ -837,7 +837,7 @@
}
},
"source": [
"This implementation is *highly* **inefficient** as small Fibonacci numbers can already take a very long time to compute. The reason for this is **exponential growth** in the number of function calls. As [PythonTutor](http://pythontutor.com/visualize.html#code=def%20fibonacci%28i%29%3A%0A%20%20%20%20if%20i%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20elif%20i%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20return%20fibonacci%28i%20-%201%29%20%2B%20fibonacci%28i%20-%202%29%0A%0Arv%20%3D%20fibonacci%285%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows, `fibonacci()` is called again and again with the same `i` arguments.\n",
"This implementation is *highly* **inefficient** as small Fibonacci numbers already take a very long time to compute. The reason for this is **exponential growth** in the number of function calls. As [PythonTutor](http://pythontutor.com/visualize.html#code=def%20fibonacci%28i%29%3A%0A%20%20%20%20if%20i%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20elif%20i%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20return%20fibonacci%28i%20-%201%29%20%2B%20fibonacci%28i%20-%202%29%0A%0Arv%20%3D%20fibonacci%285%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows, `fibonacci()` is called again and again with the same `i` arguments.\n",
"\n",
"To understand this in detail, we would have to study algorithms and data structures (e.g., with [this book](https://www.amazon.de/Introduction-Algorithms-Press-Thomas-Cormen/dp/0262033844/ref=sr_1_1?__mk_de_DE=%C3%85M%C3%85%C5%BD%C3%95%C3%91&crid=1JNE8U0VZGU0O&qid=1569837169&s=gateway&sprefix=algorithms+an%2Caps%2C180&sr=8-1)), a discipline within computer science, and dive into the analysis of **[time complexity of algorithms](https://en.wikipedia.org/wiki/Time_complexity)**.\n",
"\n",
@ -859,8 +859,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"The slowest run took 5.01 times longer than the fastest. This could mean that an intermediate result is being cached.\n",
"55 µs ± 44.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
"47.3 µs ± 12.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
]
}
],
@ -882,7 +881,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"1.63 ms ± 68 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
"1.63 ms ± 21.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
]
}
],
@ -904,7 +903,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"192 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
"211 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
]
}
],
@ -926,7 +925,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"2.21 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
"2.22 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
]
}
],
@ -948,7 +947,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"5.62 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
"5.81 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n"
]
}
],
@ -4072,17 +4071,19 @@
"source": [
"The infinite recursions could easily be avoided by replacing `n == 0` with `n <= 0` in both functions and thereby **generalizing** them. But even then, calling either `countdown()` or `factorial()` with a non-integer number is *semantically* wrong and therefore we better leave the base cases unchanged.\n",
"\n",
"Errors as above are a symptom of missing **type checking**: By design, Python allows us to pass in not only integers but objects of any type as arguments to the `countdown()` and `factorial()` functions. As long as the arguments \"behave\" like integers, we will not encounter any *runtime* errors. This is the case here as the two example functions only use the `-` and `*` operators internally and in this context a `float` object behaves exactly like an `int` object. So, the functions keep calling themselves until Python decides with a built-in heuristic that the recursion is likely not going to end and aborts the computations with a `RecursionError`. Stricly speaking, this is of course a *runtime* error as well. The missing type checking is 100% intentional and considered a feature of rather than a bug in Python.\n",
"Errors as above are a symptom of missing **type checking**: By design, Python allows us to pass in not only integers but objects of any type as arguments to the `countdown()` and `factorial()` functions. As long as the arguments \"behave\" like integers, we will not encounter any *runtime* errors. This is the case here as the two example functions only use the `-` and `*` operators internally and, in the context of arithmetic, a `float` object behaves exactly like an `int` object. So, the functions keep calling themselves until Python decides with a built-in heuristic that the recursion is likely not going to end and aborts the computations with a `RecursionError`. Stricly speaking, a `RecursionError` is of course a *runtime* error as well.\n",
"\n",
"Pythonistas often use the term **[duck typing](https://en.wikipedia.org/wiki/Duck_typing)** when refering to the same idea and the colloquial saying goes \"If it walks like a duck and it quacks like a duck, it must be a duck\". For example, we could call `factorial()` with the `float` object `3.0` and the recursion works out fine. So, as long as the `3.0` \"walks\" like a `3` and \"quacks\" like a `3`, it \"must be\" a `3`.\n",
"The missing type checking is *100% intentional* and considered a **[feature of rather than a bug](https://www.urbandictionary.com/define.php?term=It%27s%20not%20a%20bug%2C%20it%27s%20a%20feature)** in Python!\n",
"\n",
"Pythonistas often use the colloquial term **[duck typing](https://en.wikipedia.org/wiki/Duck_typing)** when refering to the same idea and the saying goes \"If it walks like a duck and it quacks like a duck, it must be a duck\". For example, we could call `factorial()` with the `float` object `3.0` and the recursion works out fine. So, as long as the `3.0` \"walks\" like a `3` and \"quacks\" like a `3`, it \"must be\" a `3`.\n",
"\n",
"We see a similar behavior when we mix objects of types `int` and `float` with arithmetic operators. For example, `1 + 2.0` works because Python implicitly views the `1` as a `1.0` at runtime and then knows how to do floating-point arithmetic: Here, the `int` \"walks\" and \"quacks\" like a `float`. Strictly speaking, this is yet another example of operator overloading whereas duck typing refers to the same behavior when passing arguments to function calls.\n",
"\n",
"The important lesson is that we must expect our functions to be called with objects of *any* type at runtime, as opposed to the one type we had in mind when we defined the function.\n",
"The important lesson is that we must expect our functions be called with objects of *any* type at runtime, as opposed to the one type we had in mind when we defined the function.\n",
"\n",
"Duck typing is possible because Python is a dynamically typed language. On the contrary, in statically typed languages like C we have to declare (i.e., \"specify\") the data type of every parameter in a function definition. Then, a `RecursionError` as for `countdown(3.1)` or `factorial(3.1)` above could not occur. For example, if we declared the `countdown()` and `factorial()` functions to only accept `int` objects, calling the functions with a `float` argument would immediately fail *syntactically*. As a downside, we would then lose the ability to call `factorial()` with `3.0`, which is *semantically* correct nevertheless.\n",
"Duck typing is possible because Python is a dynamically typed language. On the contrary, in statically typed languages like C we *must* declare (i.e., \"specify\") the data type of every parameter in a function definition. Then, a `RecursionError` as for `countdown(3.1)` or `factorial(3.1)` above could not occur. For example, if we declared the `countdown()` and `factorial()` functions to only accept `int` objects, calling the functions with a `float` argument would immediately fail *syntactically*. As a downside, we would then lose the ability to call `factorial()` with `3.0`, which is *semantically* correct nevertheless.\n",
"\n",
"So, there is no black or white answer as to which of the two language designs is better. Yet, most professional programmers have very strong opinions with respect to duck typing reaching from \"love\" to \"hate\". This is another example as to how programming is a subjective art and not an \"objective\" science. Probably, Python is regarded more beginner friendly as `3` and `3.0` should intuitively be interchangeable."
"So, there is no black or white answer as to which of the two language designs is better. Yet, most professional programmers have very strong opinions with respect to duck typing reaching from \"love\" to \"hate\". This is another example as to how programming is a subjective art rather than an \"objective\" science. Python's design is probably more appealing to beginners who intuitively regard `3` and `3.0` as interchangeable."
]
},
{
@ -4104,15 +4105,15 @@
}
},
"source": [
"We can 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 passed in. We further **validate the input** by verifying that the integer is non-negative.\n",
"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 can 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](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 can certainly be expected when mentioned in the docstring.\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 certainly be expected when mentioned in the docstring.\n",
"\n",
"Forcing `n` to be an `int` is a very puritan way of handling the issues discussed above. A more relaxed approach could be to also accept a `float` and use its [is_integer()](https://docs.python.org/3/library/stdtypes.html#float.is_integer) method to check if `n` could be casted as an `int`. After all, by being too puritan we can not take advantage of duck typing.\n",
"Forcing `n` to be an `int` is a very puritan way of handling the issues discussed above. A more relaxed approach could be to also accept a `float` and use its [is_integer()](https://docs.python.org/3/library/stdtypes.html#float.is_integer) method to check if `n` could be casted as an `int`. After all, being too puritan we cannot take advantage of duck typing.\n",
"\n",
"So in essence, we are doing *two* things here. Besides checking for the correct type, we are also enforcing **domain-specific** (i.e., mathematical) rules with respect to the non-negativity of `n`."
"So in essence, we are doing *two* things here: Besides checking the type, we also enforce **domain-specific** (i.e., mathematical here) rules with respect to the non-negativity of `n`."
]
},
{
@ -4368,7 +4369,7 @@
" n (int): seconds until the party begins; must be positive\n",
"\n",
" Raises:\n",
" TypeError: if n is not of an integer\n",
" TypeError: if n is not an integer\n",
" ValueError: if n is not positive\n",
" \"\"\"\n",
" if not isinstance(n, int):\n",
@ -4464,7 +4465,7 @@
" gcd (int)\n",
"\n",
" Raises:\n",
" TypeError: if a or b are not of an integer type\n",
" TypeError: if a or b are not integers\n",
" ValueError: if a or b are not positive\n",
" \"\"\"\n",
" if not isinstance(a, int) or not isinstance(b, int):\n",
@ -4548,7 +4549,7 @@
}
},
"source": [
"We can also see that this implementation is a lot *less* efficient than its recursive counterpart which solves `gcd()` for the same two numbers $112233445566778899$ and $987654321$ within microseconds."
"We also see that this implementation is a lot *less* efficient than its recursive counterpart which solves `gcd()` for the same two numbers $112233445566778899$ and $987654321$ within microseconds."
]
},
{
@ -4657,7 +4658,7 @@
" n (int): a positive number to start the Collatz sequence at\n",
"\n",
" Raises:\n",
" TypeError: if n is not of an integer\n",
" TypeError: if n is not an integer\n",
" ValueError: if n is not positive\n",
" \"\"\"\n",
" if not isinstance(n, int):\n",
@ -4668,7 +4669,7 @@
" while n != 1:\n",
" print(n, end=\" \")\n",
" if n % 2 == 0:\n",
" n //= 2 # //= so that n remains an int\n",
" n //= 2 # //= instead of /= so that n remains an int\n",
" else:\n",
" n = 3 * n + 1\n",
"\n",
@ -4791,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 verca 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 really 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**. Sugar makes a cup of tea taste better but we can drink tea without sugar too.\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 really 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**. Sugar makes a cup of tea taste better but we may drink tea without sugar too.\n",
"\n",
"Consider the following `numbers` list. Without the `for` statement, we would have to keep track of a temporary **index variable** `i` to loop over all its elements and also obtain the individual elements with the `[]` operator in each iteration of the loop."
"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."
]
},
{
@ -4806,7 +4807,7 @@
},
"outputs": [],
"source": [
"numbers = [5, 6, 7, 8, 9]"
"numbers = [0, 1, 2, 3, 4]"
]
},
{
@ -4822,7 +4823,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"5 6 7 8 9 "
"0 1 2 3 4 "
]
}
],
@ -4831,7 +4832,8 @@
"while i < len(numbers):\n",
" number = numbers[i]\n",
" print(number, end=\" \")\n",
" i += 1"
" i += 1\n",
"del i"
]
},
{
@ -4842,7 +4844,7 @@
}
},
"source": [
"The `for` statement, on the contrary, makes the actual business logic more apparent by stripping all the boilerplate code away."
"The `for` statement, on the contrary, makes the actual business logic more apparent by stripping all the **boilerplate code** away."
]
},
{
@ -4858,7 +4860,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"5 6 7 8 9 "
"0 1 2 3 4 "
]
}
],
@ -4875,34 +4877,12 @@
}
},
"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 will look closely at the underlying data types in Chapter 7."
"For sequences of integers the [range()](https://docs.python.org/3/library/functions.html#func-range) built-in makes the `for` statement even more convenient: It creates a list-like object of type `range` that generates integers \"on the fly\" and we will look closely at the underlying effects in memory in Chapter 7."
]
},
{
"cell_type": "code",
"execution_count": 51,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0 1 2 3 4 "
]
}
],
"source": [
"for number in [0, 1, 2, 3, 4]:\n",
" print(number, end=\" \")"
]
},
{
"cell_type": "code",
"execution_count": 52,
"metadata": {
"slideshow": {
"slide_type": "fragment"
@ -4922,20 +4902,9 @@
" print(number, end=\" \")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Let's quickly verify that `range(5)` creates an object of type `range`."
]
},
{
"cell_type": "code",
"execution_count": 53,
"execution_count": 52,
"metadata": {
"slideshow": {
"slide_type": "fragment"
@ -4948,7 +4917,7 @@
"range"
]
},
"execution_count": 53,
"execution_count": 52,
"metadata": {},
"output_type": "execute_result"
}
@ -4965,12 +4934,12 @@
}
},
"source": [
"[range()](https://docs.python.org/3/library/functions.html#func-range) takes optional `start` and `step` arguments that we can use to customize the sequence of integers even more."
"[range()](https://docs.python.org/3/library/functions.html#func-range) takes optional `start` and `step` arguments that we use to customize the sequence of integers even more."
]
},
{
"cell_type": "code",
"execution_count": 54,
"execution_count": 53,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -4992,7 +4961,7 @@
},
{
"cell_type": "code",
"execution_count": 55,
"execution_count": 54,
"metadata": {
"slideshow": {
"slide_type": "fragment"
@ -5031,26 +5000,26 @@
}
},
"source": [
"The important difference between the `list` objects (i.e., `[0, 1, 2, 3, 4]` and `[1, 3, 5, 7, 9]`) and the `range` objects (i.e., `range(5)` and `range(1, 10, 2)`) is that in the former case *six* objects are created in memory, one `list` holding pointers to *five* `int` objects, whereas in the latter case only *one* `range` object exists in memory that **generates** `int` objects as we ask for it.\n",
"The important difference between the above `list` objects, `[0, 1, 2, 3, 4]` and `[1, 3, 5, 7, 9]`, and the `range` objects, `range(5)` and `range(1, 10, 2)`, is that in the former case *six* objects are created in memory *before* the `for` statement starts running, *one* `list` holding pointers to *five* `int` objects, whereas in the latter case only *one* `range` object is created that **generates** `int` objects one at a time *while* the `for`-loop runs.\n",
"\n",
"However, we can iterate over both of them. So a natural question to ask is why Python treats objects of *different* types in the *same* way when used with a `for` statement.\n",
"However, we can loop over both of them. So a natural question to ask is why Python treats objects of *different* types in the *same* way when used with a `for` statement.\n",
"\n",
"So far, the overarching storyline in this book goes like this: In Python, *everything* is an object. Besides its *identity* and *value*, every object is characterized by belonging to *one* data type that determines how the object behaves and what we can do with it.\n",
"So far, the overarching storyline in this book goes like this: In Python, *everything* is an object. Besides its *identity* and *value*, every object is characterized by belonging to *one data type* that determines how the object behaves and what we may do with it.\n",
"\n",
"Now, just as we classify objects by their types, we also classify these **concrete data types** (e.g., `int`, `float`, or `str`) into **abstract concepts**.\n",
"Now, just as we classify objects by their types, we also classify these **concrete data types** (e.g., `int`, `float`, `str`, or `list`) into **abstract concepts**.\n",
"\n",
"We have actually done this in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) already when we described a `list` object as \"some sort of container that holds [...] pointers to other objects\". So, abstractly speaking, **containers** are any objects that are \"composed\" of other objects and also \"manage\" how these objects are organized. `list` objects, for example, have the property that they model an inherent order associated with its elements. There exist, however, many other container types, many of which do *not* come with an order. So, containers primarily \"contain\" other objects and have *nothing* to do with looping.\n",
"We actually did this already in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) when we described a `list` object as \"some sort of container that holds [...] pointers to other objects\". So, abstractly speaking, **containers** are any objects that are \"composed\" of other objects and also \"manage\" how these objects are organized. `list` objects, for example, have the property that they model an internal order associated with its elements. There exist, however, other container types, many of which do *not* come with an order. So, containers primarily \"contain\" other objects and have *nothing* to do with looping.\n",
"\n",
"On the contrary, the abstract concept of **iterables** is all about looping: Any object that we can loop over is by definition an iterable. So, `range` objects, for example, are iterables taht do *not* contain any other objects. Moreover, looping does *not* have to occur in any particular order although this is the case for both `list` and `range` objects.\n",
"On the contrary, the abstract concept of **iterables** is all about looping: Any object that we can loop over is by definition an iterable. So, `range` objects, for example, are iterables that do *not* contain other objects. Moreover, looping does *not* have to occur in a *predictable* order although this is the case for both `list` and `range` objects.\n",
"\n",
"Typically, containers are iterable and iterables are containers. Yet, only because these two concepts coincide often, we must not think of them as the same. Chapter 10 will finally give an explanation as to how abstract concepts are implemented and play together.\n",
"\n",
"`list` objects like `first_names` below are iterable containers. They actually implement even more abstract concepts as we will see in Chapter 7."
"So, `list` objects like `first_names` below are iterable containers. They actually implement even more abstract concepts as we will see in Chapter 7."
]
},
{
"cell_type": "code",
"execution_count": 56,
"execution_count": 55,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -5069,12 +5038,12 @@
}
},
"source": [
"The characteristic operator associated with a container type is the `in` operator which checks if a given object evaluates equal to any of the objects in the container. Colloquially, it checks if an object is \"contained\" in the container. This operation is also called **membership testing**."
"The characteristic operator associated with a container type is the `in` operator: It checks if a given object evaluates equal to at least one of the objects in the container. Colloquially, it checks if an object is \"contained\" in the container. Formally, this operation is called **membership testing**."
]
},
{
"cell_type": "code",
"execution_count": 57,
"execution_count": 56,
"metadata": {
"slideshow": {
"slide_type": "fragment"
@ -5087,7 +5056,7 @@
"True"
]
},
"execution_count": 57,
"execution_count": 56,
"metadata": {},
"output_type": "execute_result"
}
@ -5098,7 +5067,7 @@
},
{
"cell_type": "code",
"execution_count": 58,
"execution_count": 57,
"metadata": {
"slideshow": {
"slide_type": "-"
@ -5111,7 +5080,7 @@
"False"
]
},
"execution_count": 58,
"execution_count": 57,
"metadata": {},
"output_type": "execute_result"
}
@ -5128,12 +5097,12 @@
}
},
"source": [
"This shows the exact workings of the `in` operator: Although `7.0` is *not* in `numbers`, `7.0` evaluates equal to the `7` that is in it, which is why the following expression evaluates to `True`. So, while we could colloquially say that `numbers` \"contains\" `7.0`, it actually does not."
"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."
]
},
{
"cell_type": "code",
"execution_count": 59,
"execution_count": 58,
"metadata": {
"slideshow": {
"slide_type": "skip"
@ -5146,13 +5115,13 @@
"True"
]
},
"execution_count": 59,
"execution_count": 58,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"7.0 in numbers"
"3.0 in numbers"
]
},
{
@ -5163,12 +5132,12 @@
}
},
"source": [
"Similarly, the characteristic operation of an iterable type is that it supports iteration, for example, with a `for`-loop."
"Similarly, the characteristic operation of an iterable type is that it supports being looped over, for example, with the `for` statement."
]
},
{
"cell_type": "code",
"execution_count": 60,
"execution_count": 59,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -5196,12 +5165,12 @@
}
},
"source": [
"If we need to have an index variable in the loop's body, we use the [enumerate()](https://docs.python.org/3/library/functions.html#enumerate) built-in that takes an iterable as its argument and then generates a stream of \"pairs\" consisting of an index variable and an object provided by the iterable. There is *no* need to ever revert back to the `while` statement to loop over an iterable object."
"If we must have an index variable in the loop's body, we use the [enumerate()](https://docs.python.org/3/library/functions.html#enumerate) built-in that takes an *iterable* as its argument and then generates a \"stream\" of \"pairs\" consisting of an index variable and an object provided by the iterable. There is *no* need to ever revert back to the `while` statement to loop over an iterable object."
]
},
{
"cell_type": "code",
"execution_count": 61,
"execution_count": 60,
"metadata": {
"slideshow": {
"slide_type": "fragment"
@ -5234,7 +5203,7 @@
},
{
"cell_type": "code",
"execution_count": 62,
"execution_count": 61,
"metadata": {
"slideshow": {
"slide_type": "skip"
@ -5267,7 +5236,7 @@
},
{
"cell_type": "code",
"execution_count": 63,
"execution_count": 62,
"metadata": {
"slideshow": {
"slide_type": "skip"
@ -5280,7 +5249,7 @@
},
{
"cell_type": "code",
"execution_count": 64,
"execution_count": 63,
"metadata": {
"slideshow": {
"slide_type": "skip"
@ -5291,17 +5260,13 @@
"name": "stdout",
"output_type": "stream",
"text": [
"Achim Müller\n",
"Berthold Meyer\n",
"Carl Mayer\n",
"Diedrich Schmitt\n",
"Eckardt Schmidt\n"
"Achim Müller Berthold Meyer Carl Mayer Diedrich Schmitt Eckardt Schmidt "
]
}
],
"source": [
"for first_name, last_name in zip(first_names, last_names):\n",
" print(first_name, last_name)"
" print(first_name, last_name, end=\" \")"
]
},
{
@ -5327,12 +5292,12 @@
"\n",
"However, one advantage of calculating Fibonacci numbers in a **forwards** fashion with a `for` statement is that we could list the entire sequence in ascending order as we calculate the desired number. To show this, we added `print()` statements in `fibonacci()` below.\n",
"\n",
"We do *not* need to store the index variable in the `for`-loop's header line: That is what the underscore \"\\_\" indicates; we \"throw it away\". Also, we do not need to explicitly check if `i` is of type `int` as the [range()](https://docs.python.org/3/library/functions.html#func-range) built-in raises a `TypeError` if used with anything other than an `int`."
"We do *not* need to store the index variable in the `for`-loop's header line: That is what the underscore variable `_` indicates; we \"throw it away\"."
]
},
{
"cell_type": "code",
"execution_count": 65,
"execution_count": 64,
"metadata": {
"code_folding": [],
"slideshow": {
@ -5351,11 +5316,12 @@
" ith_fibonacci (int)\n",
"\n",
" Raises:\n",
" TypeError: if i is not of an integer type\n",
" TypeError: if i is not an integer\n",
" ValueError: if i is not positive\n",
" \"\"\"\n",
" # no need to check if i is an integer as range() does that\n",
" if i < 0:\n",
" if not isinstance(i, int):\n",
" raise TypeError(\"i must be an integer\")\n",
" elif i < 0:\n",
" raise ValueError(\"i must be non-negative\")\n",
"\n",
" a = 0\n",
@ -5372,7 +5338,7 @@
},
{
"cell_type": "code",
"execution_count": 66,
"execution_count": 65,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -5392,7 +5358,7 @@
"144"
]
},
"execution_count": 66,
"execution_count": 65,
"metadata": {},
"output_type": "execute_result"
}
@ -5420,12 +5386,12 @@
}
},
"source": [
"Another more important advantage is that now we can calculate even big Fibonacci numbers *efficiently*."
"Another more important advantage is that now we may calculate even big Fibonacci numbers *efficiently*."
]
},
{
"cell_type": "code",
"execution_count": 67,
"execution_count": 66,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -5445,7 +5411,7 @@
"218922995834555169026"
]
},
"execution_count": 67,
"execution_count": 66,
"metadata": {},
"output_type": "execute_result"
}
@ -5473,12 +5439,12 @@
}
},
"source": [
"The iterative `factorial()` function is comparable to its recursive counterpart when it comes to readability. One advantage of calculating the factorial in a forwards fashion is that we could track the intermediate `product` as it grows."
"The iterative `factorial()` implementation is comparable to its recursive counterpart when it comes to readability. One advantage of calculating the factorial in a forwards fashion is that we could track the intermediate `product` as it grows."
]
},
{
"cell_type": "code",
"execution_count": 68,
"execution_count": 67,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -5514,7 +5480,7 @@
},
{
"cell_type": "code",
"execution_count": 69,
"execution_count": 68,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -5534,7 +5500,7 @@
"3628800"
]
},
"execution_count": 69,
"execution_count": 68,
"metadata": {},
"output_type": "execute_result"
}
@ -5584,12 +5550,12 @@
}
},
"source": [
"Let's say we have a list of numbers and we want to check if the square of at least one of its numbers is above a `threshold` of `100`."
"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`."
]
},
{
"cell_type": "code",
"execution_count": 70,
"execution_count": 69,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -5602,7 +5568,7 @@
},
{
"cell_type": "code",
"execution_count": 71,
"execution_count": 70,
"metadata": {
"slideshow": {
"slide_type": "-"
@ -5621,12 +5587,12 @@
}
},
"source": [
"A first naive implementation could look like this: We loop over *every* element in `numbers` and set an **indicator variable** `is_above` to `True` once we encounter an element satisfying our search condition."
"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."
]
},
{
"cell_type": "code",
"execution_count": 72,
"execution_count": 71,
"metadata": {
"slideshow": {
"slide_type": "fragment"
@ -5663,16 +5629,16 @@
}
},
"source": [
"This implementation is *inefficient* as even if the *first* number in `numbers` has a square greater than `100`, we loop until the last element, which could take a long time for a big list.\n",
"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",
"\n",
"Moreover, we must initialize `is_above` before the `for`-loop and write an `if`-`else`-logic seperate from it to check for the final result. The actual business logic is not clear right away.\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 let's us stop the `for`-loop in any iteration of the loop. Conceptually, it is yet another means of controlling the flow of execution."
]
},
{
"cell_type": "code",
"execution_count": 73,
"execution_count": 72,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -5710,7 +5676,7 @@
}
},
"source": [
"This is a computational improvement. However, the code can still be split into *three* groups: initialization, the `for`-loop, and some finalizing logic. We would prefer to convey the program's idea in *one* compound statement."
"This is a computational improvement. However, the code still consists *three* logical sections: some initialization *before* the `for`-loop, the loop itself, and some finalizing logic. We would prefer to convey the program's idea in *one* compound statement instead."
]
},
{
@ -5732,9 +5698,9 @@
}
},
"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 only executed if the body in the `for`-branch is *not* stopped with a `break` statement before reaching the last iteration in the loop. The word \"else\" implies a rather unintuitive meaning and it had better been named a `then`-clause. In most scenarios, however, the `else`-clause logically goes together with some `if` statement within 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. The `else`-branch is executed *only if* 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 rather 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 expressive power of our code increases. Not many programming languages support a `for`-`else`-branching, which turns out to be very useful in practice."
"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."
]
},
{
@ -5750,7 +5716,7 @@
},
{
"cell_type": "code",
"execution_count": 74,
"execution_count": 73,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -5788,12 +5754,12 @@
}
},
"source": [
"Lastly, we incorporate the `if`-`else` logic at the end into the `for`-loop and avoid the `is_above` variable alltogether."
"Lastly, we incorporate the finalizing `if`-`else` logic into the `for`-loop avoiding the `is_above` variable alltogether."
]
},
{
"cell_type": "code",
"execution_count": 75,
"execution_count": 74,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -5826,12 +5792,12 @@
}
},
"source": [
"Of course, if we set the `threshold` a number'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)**, at least as long as we model the list of numbers with 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 through the entire `numbers` list. There is *no way* to further optimize this **[linear search](https://en.wikipedia.org/wiki/Linear_search)**, at least as long as we model `numbers` as a `list` object. More advanced data types, however, exist that mitigate that downside."
]
},
{
"cell_type": "code",
"execution_count": 76,
"execution_count": 75,
"metadata": {
"slideshow": {
"slide_type": "skip"
@ -5879,15 +5845,15 @@
"source": [
"Often times, we process some iterable with numeric data, for example, a list of numbers as in the introductory example in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) or, more realistically, data from a CSV file with many rows and columns.\n",
"\n",
"Processing numeric data usually comes down to operations that can be grouped into one of the following three categories:\n",
"Processing numeric data usually comes down to operations that may be grouped into one of the following three categories:\n",
"\n",
"- **mapping**: transform an observation according to some functional relationship $y = f(x)$\n",
"- **filtering**: throw away individual observations (e.g., statistical outliers)\n",
"- **reducing**: collect individual observations into summary statistics\n",
"- **mapping**: transform a sample according to some functional relationship $y = f(x)$\n",
"- **filtering**: throw away individual samples (e.g., statistical outliers)\n",
"- **reducing**: collect individual samples into summary statistics\n",
"\n",
"We will study this **map-filter-reduce** paradigm extensively in Chapter 7 after introducing more advanced data types that are needed to work with \"big\" data.\n",
"\n",
"In this section, we focus on *filtering out* some samples within a `for`-loop."
"In the remainder of this section, we focus on *filtering out* some samples within a `for`-loop."
]
},
{
@ -5919,7 +5885,7 @@
},
{
"cell_type": "code",
"execution_count": 77,
"execution_count": 76,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -5932,7 +5898,7 @@
},
{
"cell_type": "code",
"execution_count": 78,
"execution_count": 77,
"metadata": {
"slideshow": {
"slide_type": "-"
@ -5952,7 +5918,7 @@
"370"
]
},
"execution_count": 78,
"execution_count": 77,
"metadata": {},
"output_type": "execute_result"
}
@ -5977,9 +5943,9 @@
}
},
"source": [
"The above code is still rather easy to read as it only involves two levels of indentation.\n",
"The above code is easy to read as it involves only two levels of indentation.\n",
"\n",
"In general, code gets harder to comprehend the more **horizontal space** it occupies. It is commonly considered good practice to grow a program **vertically** rather than horizontally. Code complient with [PEP 8](https://www.python.org/dev/peps/pep-0008/#maximum-line-length) requires us to use *at most* 79 characters in a line!\n",
"In general, code gets harder to comprehend the more **horizontal space** it occupies. It is commonly considered good practice to grow a program **vertically** rather than horizontally. Code compliant with [PEP 8](https://www.python.org/dev/peps/pep-0008/#maximum-line-length) requires us to use *at most* 79 characters in a line!\n",
"\n",
"Consider the next example, whose implementation in code already starts to look \"unbalanced\"."
]
@ -6014,7 +5980,7 @@
},
{
"cell_type": "code",
"execution_count": 79,
"execution_count": 78,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -6027,7 +5993,7 @@
"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]"
]
},
"execution_count": 79,
"execution_count": 78,
"metadata": {},
"output_type": "execute_result"
}
@ -6038,7 +6004,7 @@
},
{
"cell_type": "code",
"execution_count": 80,
"execution_count": 79,
"metadata": {
"slideshow": {
"slide_type": "-"
@ -6058,7 +6024,7 @@
"182"
]
},
"execution_count": 80,
"execution_count": 79,
"metadata": {},
"output_type": "execute_result"
}
@ -6084,11 +6050,11 @@
}
},
"source": [
"With already three levels of indentation, less horizontal space is available for the actual code. Of course, one could combine 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). However, then we trade off horizontal space against a more \"complex\" `if` logic and this is not a real improvement.\n",
"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 right into the next iteration skipping the rest of the code block.\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",
"\n",
"The revised code fragement below occupies more vertical space and less horizontal space. A good trade-off."
"The revised code fragement below occupies more vertical space and less horizontal space: A *good* trade-off."
]
},
{
@ -6104,7 +6070,7 @@
},
{
"cell_type": "code",
"execution_count": 81,
"execution_count": 80,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -6124,7 +6090,7 @@
"182"
]
},
"execution_count": 81,
"execution_count": 80,
"metadata": {},
"output_type": "execute_result"
}
@ -6153,13 +6119,11 @@
}
},
"source": [
"This is yet another illustration of why programming is an art. The two preceding code fragments do *exactly* the *same* with *identical* time complexity. However, arguably the latter it a lot easier to read for a human, at least when the business logic grows beyond more than just two nested filters.\n",
"This is yet another illustration of why programming is an art. The two preceding code cells do *exactly* the *same* with *identical* time complexity. However, the latter is arguably easier to read for a human, even more so when the business logic grows beyond two nested filters.\n",
"\n",
"The idea behind the `continue` statement is conceptually similar to the early exit pattern we saw in the context of function definitions in [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb).\n",
"The idea behind the `continue` statement is conceptually similar to the early exit pattern we saw above.\n",
"\n",
"The two examples can be modeled in an even better way as we will see in Chapter 7.\n",
"\n",
"Both the `break` and `continue` statements as well as the optional `else`-clause are not only supported within `for`-loops but also `while`-loops."
"Both the `break` and `continue` statements as well as the optional `else`-clause are not only supported with `for`-loops but also `while`-loops."
]
},
{
@ -6181,7 +6145,7 @@
}
},
"source": [
"Sometimes we find ourselves in situations where we can *not* know ahead of time how often or until which point in time a code block is to be executed."
"Sometimes we find ourselves in situations where we *cannot* know ahead of time how often or until which point in time a code block is to be executed."
]
},
{
@ -6205,7 +6169,7 @@
"source": [
"Let's consider a game where we randomly choose a variable to be either \"Heads\" or \"Tails\" and the user of our program has to guess it.\n",
"\n",
"Python provides the built-in [input()](https://docs.python.org/3/library/functions.html#input) function that prints a message to the user, called the **prompt**, and reads in what the user typed in response as a `str` object. We use it to process the user's \"unpredictable\" input to our program (i.e., a user might type in some invalid response). Further, we use the [random()](https://docs.python.org/3/library/random.html#random.random) function in the [random](https://docs.python.org/3/library/random.html) module to model the coin toss.\n",
"Python provides the built-in [input()](https://docs.python.org/3/library/functions.html#input) function that prints a message to the user, called the **prompt**, and reads in what was typed in response as a `str` object. We use it to process a user's \"unreliable\" input to our program (i.e., a user might type in some invalid response). Further, we use the [random()](https://docs.python.org/3/library/random.html#random.random) function in the [random](https://docs.python.org/3/library/random.html) module to model the coin toss.\n",
"\n",
"A popular pattern to approach such **indefinite loops** is to go with a `while True` statement which on its own would cause Python to enter into an infinite loop. Then, once a certain event occurs, we `break` out of the loop.\n",
"\n",
@ -6214,7 +6178,7 @@
},
{
"cell_type": "code",
"execution_count": 82,
"execution_count": 81,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -6227,7 +6191,7 @@
},
{
"cell_type": "code",
"execution_count": 83,
"execution_count": 82,
"metadata": {
"slideshow": {
"slide_type": "-"
@ -6240,7 +6204,7 @@
},
{
"cell_type": "code",
"execution_count": 84,
"execution_count": 83,
"metadata": {
"code_folding": [],
"slideshow": {
@ -6285,10 +6249,10 @@
}
},
"source": [
"This version has two *severe* aspects where we should improve on:\n",
"This version exhibits two *severe* issues where we should improve on:\n",
"\n",
"1. If a user enters something other than \"heads\" or \"tails\", for example, \"Heads\" or \"Tails\", the program keeps running without the user knowing about the mistake!\n",
"2. It intermingles the coin tossing with the comparison against the user's input: Mixing unrelated business logic in the same code fragment makes a program harder to read and maintain in the long run."
"1. If a user enters *anything* other than `\"heads\"` or `\"tails\"`, for example, `\"Heads\"` or `\"Tails\"`, the program keeps running *without* the user knowing about the mistake!\n",
"2. The code *intermingles* the coin tossing with the processing of the user's input: Mixing *unrelated* business logic in the *same* code block makes a program harder to read and, more importantly, maintain in the long run."
]
},
{
@ -6310,16 +6274,16 @@
}
},
"source": [
"Let's refactor the code and make it *modular* and *comprehendable*.\n",
"Let's refactor the code and make it *modular*.\n",
"\n",
"First, we divide the logic into two functions `get_guess()` and `toss_coin()` that are controlled from within a `while`-loop.\n",
"First, we divide the business logic into two functions `get_guess()` and `toss_coin()` that are controlled from within a `while`-loop.\n",
"\n",
"`get_guess()` not only reads in the user's input but also implements a simple input validation pattern in that the [strip()](https://docs.python.org/3/library/stdtypes.html?highlight=__contains__#str.strip) and [lower()](https://docs.python.org/3/library/stdtypes.html?highlight=__contains__#str.lower) methods remove preceeding and trailing whitespace and lower case the input ensuring that the user can spell the input in any possible way (e.g. all upper or lower case). Also, `get_guess()` checks if the user entered one of the two valid options. If so, it returns either `\"heads\"` or `\"tails\"`; if not, it returns `None`."
"`get_guess()` not only reads in the user's input but also implements a simple input validation pattern in that the [strip()](https://docs.python.org/3/library/stdtypes.html?highlight=__contains__#str.strip) and [lower()](https://docs.python.org/3/library/stdtypes.html?highlight=__contains__#str.lower) methods remove preceeding and trailing whitespace and lower case the input ensuring that the user may spell the input in any possible way (e.g. all upper or lower case). Also, `get_guess()` checks if the user entered one of the two valid options. If so, it returns either `\"heads\"` or `\"tails\"`; if not, it returns `None`."
]
},
{
"cell_type": "code",
"execution_count": 85,
"execution_count": 84,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -6351,12 +6315,12 @@
}
},
"source": [
"`toss_coin()` is a simple function that models a fair coin toss when called with default arguments."
"`toss_coin()` models a fair coin toss when called with default arguments."
]
},
{
"cell_type": "code",
"execution_count": 86,
"execution_count": 85,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -6387,14 +6351,14 @@
}
},
"source": [
"Second, we rewrite the `if`-`else`-logic to explictly handle the case where `get_guess()` returns `None`: Whenever the user enters something invalid, a warning is shown, and another try is granted. Observe how we use the `is` operator and not the `==` operator as `None` is a singleton object.\n",
"Second, we rewrite the `if`-`else`-logic to explictly handle the case where `get_guess()` returns `None`: Whenever the user enters something invalid, a warning is shown, and another try is granted. We use the `is` operator and not the `==` operator as `None` is a singleton object.\n",
"\n",
"The `while` statement itself takes on the role of **glue code** that only manages how other parts of the program interact with each other."
"The `while`-loop takes on the role of **glue code** that manages how other parts of the program interact with each other."
]
},
{
"cell_type": "code",
"execution_count": 87,
"execution_count": 86,
"metadata": {
"slideshow": {
"slide_type": "skip"
@ -6407,7 +6371,7 @@
},
{
"cell_type": "code",
"execution_count": 88,
"execution_count": 87,
"metadata": {
"slideshow": {
"slide_type": "slide"
@ -6447,7 +6411,7 @@
}
},
"source": [
"Now, our little program's business logic is a lot easier to comprehend. More importantly, we can now easily make changes to the program. For example, we could make the `toss_coin()` function base the tossing on a probability distribution other than the uniform (i.e., replace the [random.random()](https://docs.python.org/3/library/random.html#random.random) function with another one). In general, a modular architecture leads to improved software maintenance."
"Now, the program's business logic is expressed in a clearer way. More importantly, we can now change it more easily. For example, we could make the `toss_coin()` function base the tossing on a probability distribution other than the uniform (i.e., replace the [random.random()](https://docs.python.org/3/library/random.html#random.random) function with another one). In general, a modular architecture leads to improved software maintenance."
]
},
{
@ -6473,11 +6437,11 @@
"\n",
"There are two redundant approaches of achieving that.\n",
"\n",
"First, we can combine functions that call themselves with conditional statements. This concept is known as **recursion** and suffices to control the flow of execution in *every* way we desire. For a beginner, this approach of **backwards** reasoning might not be intuitive but it turns out to be a very useful tool to have in one's toolbox.\n",
"First, we combine functions that call themselves with conditional statements. This concept is known as **recursion** and suffices to control the flow of execution in *every* way we desire. For a beginner, this approach of **backwards** reasoning might not be intuitive but it turns out to be a very useful tool to have in one's toolbox.\n",
"\n",
"Second, the `while` and `for` statements are alternative and potentially more intuitive ways to express iteration as they correspond to a **forwards** reasoning. The `for` statement is **syntactic sugar** that allows to rewrite common occurences of the `while` statement in a concise way. Python provides the `break` and `continue` statements as well as an optional `else`-clause that make working with the `for` and `while` statements even more convenient.\n",
"\n",
"**Iterables** are any **concrete data types** that support being looped over, for example, with the `for` statement. The idea behind iterables is an **abstract concept** that may or may not be implemented by any given concrete data type. For example, both `list` and `range` objects can be looped over. The `list` type is also a **container** as any given `list` objects \"contains\" pointers to other objects in memory. On the contrary, the `range` type does not point to any other objects but rather creates new `int` objects \"on the fly\"."
"**Iterables** are any **concrete data types** that support being looped over, for example, with the `for` statement. The idea behind iterables is an **abstract concept** that may or may not be implemented by any given concrete data type. For example, both `list` and `range` objects can be looped over. The `list` type is also a **container** as any given `list` objects \"contains\" pointers to other objects in memory. On the contrary, the `range` type does not point to any other objects but rather creates *new* `int` objects \"on the fly\" (i.e., when being looped over)."
]
}
],

View file

@ -420,7 +420,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"In words, this means that in order to move a tower consisting of $n$ disks from an `origin` $o$ to a `destination` $d$, we three steps must be executed:\n",
"In words, this means that in order to move a tower consisting of $n$ disks from an `origin` $o$ to a `destination` $d$, three steps must be executed:\n",
"\n",
"1. Move the top most $n - 1$ disks of the tower temporarily from $o$ to $i$ (= sub-problem 1)\n",
"2. Move the remaining and largest disk from $o$ to $d$\n",
@ -577,7 +577,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.9**: Copy the base case from `sol()`."
"**Q15.9**: Copy the base case from `sol()`!"
]
},
{