{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Lesson A7 – Iterations" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It is a common purpose of programs to perform a defined task many, many times. We will learn in this part, how to write code that repeats itself a number of times without being literately written multiple times." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's demonstrate this with an example that we will explain in more detail later using a dictionary of solvents and boiling points as in the container lesson." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "solvents_dict = {\n", " \"water\": 100,\n", " \"ethanol\": 78,\n", " \"N,N-dimethylformamide\": 153,\n", " \"dichloromethane\": 40,\n", " }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We could print each entry in the dictionary with a separate line: " ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The boling point of water is 100 °C\n", "The boling point of ethanol is 78 °C\n", "The boling point of N,N-dimethylformamide is 153 °C\n", "The boling point of dichloromethane is 40 °C\n" ] } ], "source": [ "# Imaging doing this for hundreds of entries ...\n", "# Or imagine you do not know all the dictionary keys before hand ...\n", "# Or imagine the keys are changing over time ...\n", "print(f\"The boling point of water is {solvents_dict['water']} °C\")\n", "print(f\"The boling point of ethanol is {solvents_dict['ethanol']} °C\")\n", "print(f\"The boling point of N,N-dimethylformamide is {solvents_dict['N,N-dimethylformamide']} °C\")\n", "print(f\"The boling point of dichloromethane is {solvents_dict['dichloromethane']} °C\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "But it would be far less tedious and error-prone, if we used a self-repeating code block for this:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The boiling point of water is 100 °C\n", "The boiling point of ethanol is 78 °C\n", "The boiling point of N,N-dimethylformamide is 153 °C\n", "The boiling point of dichloromethane is 40 °C\n" ] } ], "source": [ "# With a loop you can write: \n", "for k, v in solvents_dict.items(): \n", " print(f\"The boiling point of {k} is {v} °C\")\n", "\n", "# You need to write much less code\n", "# It works for arbitrarily many entries\n", "# You do not need to know the entries beforehand" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## For-loops" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One way to implement a repeating code block, is to use *for*-loops. They are\n", "meant to be used whenever you want to do something for a pre-defined number of times.\n", "In Python, for loops are connected to *iterables*. An iterable is an object you can use to\n", "iterate over. Collections, like lists, can be for example iterables. We use a container for an iteration with the pattern: `for` *element* `in` *iterable*." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "2\n", "3\n" ] } ], "source": [ "for i in [1, 2, 3]: # <- colon after the for statement\n", " # loop over elements\n", " print(i)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "**Note:** Notice, that the code block after the `for` statement, which should be repeated, is __indented__ by 4 spaces. As we saw for if-else statements, indentation is also important in this new context to mark code as subordinate to the loop.\n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When the interpreter encounters the `for` directive, it takes the first element\n", "of the following iterable and assigns it to the control variable `i` (which does\n", "not need to be defined before) and executes the indented code block. After the\n", "code block ended, the interpreter jumps back to the beginning of the loop and assigns\n", "the next element of the iterable to `i` and so on until the end of the iterable is reached." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "**Advanced:** If you have some familiarity with other programming languages this might seem strange to you as\n", "for loop are often implemented differently. Just as a side-note, the following example\n", "does not work in Python:\n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "```python\n", ">>> i = 0 # initialise control variable\n", "... for i < 2: # check if control variable meets condition\n", "... print(i) # do something\n", "... i += 1 # modify control variable\n", " \n", "File \"\", line 2\n", " for i < 2: # check if control variable meets condition\n", " ^\n", "SyntaxError: invalid syntax\n", "```\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Use for-loops in Python as iterations over an iterable with the `in` operator. Do not set and modify the\n", "control variable explicitly. If you are iterating and you do not use the control\n", "variable, try to make this clear by using a single underscore `_`." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello!\n", "Hello!\n", "Hello!\n" ] } ], "source": [ "for _ in [1, 1, 1]:\n", " # loop over elements but do something unrelated\n", " print(\"Hello!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As another rule of thumb, do not mess with the iterable you are looping over\n", "during the loops." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "2\n", "3\n", "4\n", "4\n" ] } ], "source": [ "l = [1, 2, 3]\n", "for i in l:\n", " if i < 3:\n", " l.append(4) # Don't!\n", " print(i)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can use other types of collections and objects you know for iterations." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "H-E-L-L-O-,- -W-O-R-L-D-!-" ] } ], "source": [ "string = \"Hello, World!\"\n", "for i in string:\n", " print(i.upper(), end=\"-\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that you can also use collections that are not sequences for this." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "4\n", "9\n" ] } ], "source": [ "set_ = {1, 2, 3}\n", "for i in set_:\n", " print(i ** 2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Especially interesting is the use of dictionaries as iterables. Consider once more our example from the introduction." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "water\n", "ethanol\n", "N,N-dimethylformamide\n", "dichloromethane\n" ] } ], "source": [ "solvents_dict = {\n", " \"water\": 100,\n", " \"ethanol\": 78,\n", " \"N,N-dimethylformamide\": 153,\n", " \"dichloromethane\": 40,\n", " }\n", "\n", "for i in solvents_dict:\n", " print(i)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Looping over a dictionary is the same as looping over its keys. You can of course\n", "loop over the values, or keys and values at the same time instead." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Loop over keys:\n", " Key: water\n", " Key: ethanol\n", " Key: N,N-dimethylformamide\n", " Key: dichloromethane\n", "\n", "Loop over values:\n", " Value: 100\n", " Value: 78\n", " Value: 153\n", " Value: 40\n", "\n", "Loop over items:\n", " Key: water, Value: 100\n", " Key: ethanol, Value: 78\n", " Key: N,N-dimethylformamide, Value: 153\n", " Key: dichloromethane, Value: 40\n" ] } ], "source": [ "print(\"Loop over keys:\")\n", "for k in solvents_dict.keys():\n", " print(f\" Key: {k}\")\n", "\n", "print(\"\\nLoop over values:\")\n", "for v in solvents_dict.values():\n", " print(f\" Value: {v}\")\n", "\n", "print(\"\\nLoop over items:\")\n", "for k, v in solvents_dict.items():\n", " print(f\" Key: {k}, Value: {v}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Break and continue" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Two Python directives associated with loops come in handy when you want\n", "to tune the looping behaviour. The `continue` directive leads to a skip of\n", "the current iteration, when it is encountered. The `break` directive aborts\n", "the current loop completely. Both are commonly used conditionally." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "ename": "NameError", "evalue": "name 'dict_' is not defined", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mfor\u001b[0m \u001b[0mk\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mv\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mdict_\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mitems\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mk\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m\"water\"\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;32mcontinue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mv\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m153\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32mbreak\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mNameError\u001b[0m: name 'dict_' is not defined" ] } ], "source": [ "for k, v in dict_.items():\n", " if k == \"water\":\n", " continue\n", " if v == 153:\n", " break\n", " print(f\" Key: {k}, Value: {v}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Also important in this regard is the `else` statement. Used at the end of a loop, it introduces a code block that is only executed, if the loop ends normally without being interrupted by a `break`. " ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "`i` was never 10\n" ] } ], "source": [ "for i in range(10):\n", " if i == 10:\n", " print(\"Aborting the loop\")\n", " break\n", "else:\n", " print(\"`i` was never 10\")" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Aborting the loop\n" ] } ], "source": [ "for i in range(10):\n", " if i == 3:\n", " print(\"Aborting the loop\")\n", " break\n", "else:\n", " print(\"`i` was never 3\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Do you see how important *identation* is here? Indenting the `else` block one hierarchy level down, completely changes the code!" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "`i` was never 3\n", "`i` was never 3\n", "`i` was never 3\n", "Aborting the loop\n" ] } ], "source": [ "for i in range(10):\n", " if i == 3:\n", " print(\"Aborting the loop\")\n", " break\n", " else:\n", " print(\"`i` was never 3\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## range() and enumerate()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Iterations over collections are nice, but what if you do not have a suitable\n", "collection at you disposal? Imagine you wanted to repeat a task a hundred times. \n", "It would be tedious to create let's say a list with a hundred elements manually first for this.\n", "This is what `range()` is for. This function gives you an iterable of integers following\n", "the pattern `range(start, stop, step`)." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "6\n", "7\n", "8\n", "9\n" ] } ], "source": [ "for i in range(10):\n", " # loop over ten elements \n", " # start = 0 # inclusive\n", " # end = 10 # exclusive\n", " # step = 1\n", " print(i)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10 - 20 - 30 - 40 - 50 - 60 - 70 - 80 - 90 - 100 - " ] } ], "source": [ "for i in range(10, 101, 10):\n", " print(i, end=\" - \")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Another super useful thing, if you have a collection at hand for an iteration\n", "over its elements but at the same time you want to use the index of the iteration,\n", "is the `enumerate()` function. It wraps your iterable with a counter and gives you\n", "an iterable of counter, element tuples. Sound difficult? It is not:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Use enumerate starting at 0:\n", " Index: 0 -- Element: water\n", " Index: 1 -- Element: ethanol\n", " Index: 2 -- Element: dichloromethane\n" ] } ], "source": [ "solven_list = [\"water\", \"ethanol\", \"dichloromethane\"]\n", "\n", "print(\"Use enumerate starting at 0:\")\n", "for c, i in enumerate(solven_list):\n", " print(f\" Index: {c} -- Element: {i}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## While-loop" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Another way we could realise a repeating code block is to use a *while* directive. Imagine you want to do a specific task more than once, but you do not exactly know how many times. You know, however, that you want to do it as long as a certain condition is met (or not met)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Let's look at this with an example. Suppose, a potential energy function $E(x)$ is not given as an analytic expression, but only as energy values at discrete values of $x$ in the interval $[-10, 10]$." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x: [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n", "E: [98, 79, 62, 47, 34, 23, 14, 7, 2, -1, -2, -1, 2, 7, 14, 23, 34, 47, 62, 79, 98]\n" ] } ], "source": [ "x = list(range(-10, 11))\n", "E = [x**2 - 2 for x in x]\n", "print(\"x: \", x)\n", "print(\"E: \", E)\n", "# E(-10) = 98, E(-9)=79 etc." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Presuming that the energy is positive at the lowest value of $x$, we want to go through the list of energies to find out if the energy crosses the zero-line. If this is the case we want to print the two $x$ values in between which the cross happens. " ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "E(-10) = 98\n", "E(-9) = 79\n", "E(-8) = 62\n", "E(-7) = 47\n", "E(-6) = 34\n", "E(-5) = 23\n", "E(-4) = 14\n", "E(-3) = 7\n", "E(-2) = 2\n", "E(x) crosses the zero line between x = -2 and x = -1.\n" ] } ], "source": [ "i = 0 # start at the beginning of the list\n", "\n", "# Check whether the potential energy of the current x value\n", "# is greater than zero\n", "while E[i] > 0:\n", " # If this is the case,\n", " # print the potential energy of the current x-value\n", " print(f\"E({x[i]}) = {E[i]}\")\n", " \n", " # and move the index to the next x-value.\n", " i += 1\n", " \n", " # If you reach the end of the list, stop iterating\n", " if i == len(x): \n", " break\n", " \n", "# Print the result\n", "if i != len(x):\n", " print(f\"E(x) crosses the zero line between x = {x[i-1]} and x = {x[i]}.\") \n", "else: \n", " print(\"E(x) does not cross the zero-line.\") " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we mentioned, while-loops are intended for situations where you want to loop for an undefined number of times. A frequent pattern that is seen in this context is:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Loop as long as necessarry...\n", "Loop as long as necessarry...\n", "Loop as long as necessarry...\n" ] } ], "source": [ "l = [2, 1, 3]\n", "\n", "element = 0\n", "while True:\n", " print(\"Loop as long as necessarry...\")\n", " \n", " if l[element]**2 > 8:\n", " break\n", " element += 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The condition after the while-directive (True) is of course always True. This initialises a\n", "potentially infinite loop. It emphasises that we do not intend to loop a certain number of times.\n", "It is easy to get hung up in a loop that goes on forever with this. The only way to get out, is the\n", "`break` directive. Once this is encountered we escape the loop immediately. In our example we go through\n", "the elements of a list and check if they exceed 8 when they are raised to the power of 2. Once we found an element that fulfills the criterion, we stop looping." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "**Note:** Use a __while__-loop if you want to repeat a code block an indefinite number of times. Take care to prevent infinite loops. \n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Advanced" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here is another while-loop example for illustration." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1, 2, 3]\n" ] } ], "source": [ "l = [2, 1, 3] # List of integers\n", "\n", "element = 0\n", "while element < (len(l) - 1) and l[element] > l[element + 1]:\n", " # Condition for loop execution: \n", " # List element is at most second last and smaller than the next \n", " l[element], l[element + 1] = l[element + 1], l[element]\n", " element += 1\n", " # Switch elements\n", " \n", "print(l)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let this code fragment sink in for a minute. What does it do? First, we create\n", "a list of numbers. Than we set a counter or index variable `element` to `0`, so to the first\n", "list element. When the interpreter meets the `while` directive, it checks the fulfillment\n", "of the following conditions: Is the element index (0) smaller than the length of the list\n", "(`len(l)`) minus 1 ($3 - 1 = 2 \\rightarrow 2 > 0 \\rightarrow$ `True`), and is the list element greater\n", "by value than the element with the next higher list index ($2 > 1 \\rightarrow$ `True`). If both\n", "conditions are met, which is the case in the first run-through, the indented code block is executed.\n", "The pattern `a, b = b, a` is the fastest way to switch two variable values. So in our case\n", "the two list elements change places, so that the larger element is moved to the right. After the\n", "position change is completed we increase the element index. After the execution of the indented statements, the interpreter jumps back to the position where the while condition is checked. This time the check evaluates to False, because the list element 2 is not larger than 3. As a consequence the indented code block is not executed and the interpreter moves on to what comes after it. What we did here, is a quite naive approach to bring the list elements in order." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Advanced" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Generators, Iterators and other curious creatures" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the context of containers and iterations we faced the important term *iterable*. An iterable is anything\n", "you can use for element wise iterations. We know that indexable *sequences* (like strings or lists) can be iterables as well as *unordered* collections (like sets and dictionaries). We also saw that `range()` for examples gives us something we can use for iterations, so in other words `range()` gives us an iterable. Let's have a closer look at this function." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "range(0, 10)\n" ] } ], "source": [ "r = range(10)\n", "print(r)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So what is this exactly? The call of the `range()` function gives us something special: A range object." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "range" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(r)" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "isinstance(r, list) # ?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A range object is not a list or any other type of collection we now. It is an object of\n", "this special type returned by `range()`. The elements we are iterating over are,\n", "however, somewhat hidden in this object. We can convert this range into a list, to expose them." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list(r)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A range object also acts as a sequence in the sense that we can request its elements by index." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "r[1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The really special thing about this is, that the elements the range object contains are not\n", "present (i.e. stored in memory) somewhere before they are used." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "**Note:** The `range()` function returns a `range` object which can be used as a special type of iterable. \n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Remember to call `help()` on anything you want to learn more about." ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Help on class range in module builtins:\n", "\n", "class range(object)\n", " | range(stop) -> range object\n", " | range(start, stop[, step]) -> range object\n", " | \n", " | Return an object that produces a sequence of integers from start (inclusive)\n", " | to stop (exclusive) by step. range(i, j) produces i, i+1, i+2, ..., j-1.\n", " | start defaults to 0, and stop is omitted! range(4) produces 0, 1, 2, 3.\n", " | These are exactly the valid indices for a list of 4 elements.\n", " | When step is given, it specifies the increment (or decrement).\n", " | \n", " | Methods defined here:\n", " | \n", " | __bool__(self, /)\n", " | self != 0\n", " | \n", " | __contains__(self, key, /)\n", " | Return key in self.\n", " | \n", " | __eq__(self, value, /)\n", " | Return self==value.\n", " | \n", " | __ge__(self, value, /)\n", " | Return self>=value.\n", " | \n", " | __getattribute__(self, name, /)\n", " | Return getattr(self, name).\n", " | \n", " | __getitem__(self, key, /)\n", " | Return self[key].\n", " | \n", " | __gt__(self, value, /)\n", " | Return self>value.\n", " | \n", " | __hash__(self, /)\n", " | Return hash(self).\n", " | \n", " | __iter__(self, /)\n", " | Implement iter(self).\n", " | \n", " | __le__(self, value, /)\n", " | Return self<=value.\n", " | \n", " | __len__(self, /)\n", " | Return len(self).\n", " | \n", " | __lt__(self, value, /)\n", " | Return self integer -- return number of occurrences of value\n", " | \n", " | index(...)\n", " | rangeobject.index(value, [start, [stop]]) -> integer -- return index of value.\n", " | Raise ValueError if the value is not present.\n", " | \n", " | ----------------------------------------------------------------------\n", " | Data descriptors defined here:\n", " | \n", " | start\n", " | \n", " | step\n", " | \n", " | stop\n", "\n" ] } ], "source": [ "help(range)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Besides `range()` we used above the `enumerate()` function. This is giving us\n", "yet another very special thing." ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n" ] } ], "source": [ "e = enumerate(range(10), 1)\n", "print(e)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So what is this again? As for range we can see from the print statement that we\n", "received an enmurate object from our function call. This enumerate object has some similarity\n", "with the range object in that it is an iterable whose elements are somewhat hidden and not\n", "really physically stored anywhere until they are used." ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[(1, 0),\n", " (2, 1),\n", " (3, 2),\n", " (4, 3),\n", " (5, 4),\n", " (6, 5),\n", " (7, 6),\n", " (8, 7),\n", " (9, 8),\n", " (10, 9)]" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list(e)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The enumerate object is different to the range object on the other hand as we can for example\n", "not treat it as a sequence by itself and we can not use an index." ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "'enumerate' object is not subscriptable", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0me\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: 'enumerate' object is not subscriptable" ] } ], "source": [ "e[1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "But what is this?! Here is anther strange thing about this enumerate object. When we try to get its elements\n", "as a list againg we get an empty list." ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[]" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list(e)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Objects like the enumerate object can only give to you each element once. It is designed to do so. After we have received every elements, the object is exhausted and remains empty. We can call this object an *iterator*. For now let's say iterators are yet another type of iterable and leave it as that. We can request the elements of an iterator one by one using the `next()` function." ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(1, 0)\n", "(2, 1)\n", "(3, 2)\n", "(4, 3)\n", "(5, 4)\n", "(6, 5)\n", "(7, 6)\n", "(8, 7)\n", "(9, 8)\n", "(10, 9)\n" ] }, { "ename": "StopIteration", "evalue": "", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mStopIteration\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0me\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0menumerate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m10\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;32mwhile\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0me\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mStopIteration\u001b[0m: " ] } ], "source": [ "e = enumerate(range(10), 1)\n", "while True:\n", " print(next(e))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Once an iterator has been exhausted, trying to squeeze another element out of it will\n", "raise an execption." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "**Note:** The `enumerate()` function returns an `enumerate` object which can be used as a special type of iterable. An `enumerate` object is an iterator that can return its elements one by one only once before it is exhausted.\n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the container lesson you have already met another function that creates an iterator: `zip()`." ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n" ] } ], "source": [ "z = zip([1, 2, 3], [\"a\", \"b\", \"c\"])\n", "print(z)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Functions that create iterators can be called generators. We do not want to dive to deep into\n", "this advanced topic but let us finish with one last very useful thing that you can use in very different\n", "situations: *Generator expressions*. With a generator expression we can create our own iterator in basically one line. Such an iterator is a good thing to have if you want to create an iterable for on-time use only,\n", "that is very memory efficient (only returns one element at the time). A generator expression resembles pretty much what we have seen as a for loop:" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " at 0x7fed68217200>\n" ] } ], "source": [ "g = (x for x in \"abc\")\n", "print(g)" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a\n", "b\n", "c\n" ] }, { "ename": "StopIteration", "evalue": "", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mStopIteration\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mwhile\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mStopIteration\u001b[0m: " ] } ], "source": [ "while True:\n", " print(next(g))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also interprete a generator expression as a list expression, when we substitute the\n", "parentheses with square brackets." ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]\n" ] } ], "source": [ "gl = [x/10 for x in range(1, 11)] # Create a range of floats\n", "print(gl)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Again, we don't want to discuss this useful but maybe confusing thing here in detail.\n", "But should you encounter such an expression any time soon, you know at least what lies before you." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.10" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": true, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": { "height": "calc(100% - 180px)", "left": "10px", "top": "150px", "width": "165px" }, "toc_section_display": true, "toc_window_display": false }, "varInspector": { "cols": { "lenName": 16, "lenType": 16, "lenVar": 40 }, "kernels_config": { "python": { "delete_cmd_postfix": "", "delete_cmd_prefix": "del ", "library": "var_list.py", "varRefreshCmd": "print(var_dic_list())" }, "r": { "delete_cmd_postfix": ") ", "delete_cmd_prefix": "rm(", "library": "var_list.r", "varRefreshCmd": "cat(var_dic_list()) " } }, "types_to_exclude": [ "module", "function", "builtin_function_or_method", "instance", "_Feature" ], "window_display": false } }, "nbformat": 4, "nbformat_minor": 2 }