Streamline previous content

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

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)."
]
}
],