{ "cells": [ { "cell_type": "markdown", "metadata": { "cell_style": "center", "slideshow": { "slide_type": "slide" } }, "source": [ "# Generator" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "-" }, "tags": [ "remove-cell" ] }, "source": [ "**CS1302 Introduction to Computer Programming**\n", "___" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "ExecuteTime": { "end_time": "2020-11-27T11:20:04.656873Z", "start_time": "2020-11-27T11:20:04.651575Z" }, "slideshow": { "slide_type": "fragment" }, "tags": [ "remove-cell" ] }, "outputs": [], "source": [ "%reload_ext mytutor" ] }, { "cell_type": "markdown", "metadata": { "hide_input": true, "slideshow": { "slide_type": "slide" } }, "source": [ "## Recursion" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Consider computing the [Fibonacci number](https://en.wikipedia.org/wiki/Fibonacci_number) of order $n$:\n", "\n", "$$\n", "F_n := \n", "\\begin{cases}\n", "F_{n-1}+F_{n-2} & n>1 \\kern1em \\text{(recurrence)}\\\\\n", "1 & n=1 \\kern1em \\text{(base case)}\\\\\n", "0 & n=0 \\kern1em \\text{(base case)}.\n", "\\end{cases}$$\n", "Fibonacci numbers have practical applications in generating [pseudorandom numbers](https://en.wikipedia.org/wiki/Lagged_Fibonacci_generator)." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "**Can we define the function by calling the function itself?**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Try stepping through such a function below to see how it works:" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:46.140516Z", "start_time": "2020-10-31T02:43:46.133048Z" }, "code_folding": [], "slideshow": { "slide_type": "fragment" }, "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n" ] }, { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%mytutor -r -h 450\n", "def fibonacci(n):\n", " if n > 1:\n", " return fibonacci(n - 1) + fibonacci(n - 2) # recursion\n", " elif n == 1:\n", " return 1\n", " else:\n", " return 0\n", "\n", "\n", "print(fibonacci(2))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "A function that calls itself (*recurs*) is called a [*recursion*](https://en.wikipedia.org/wiki/Recursion_(computer_science))." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "**Exercise** Write a function `gcd` that implements the [Euclidean algorithm for the greatest common divisor](https://en.wikipedia.org/wiki/Euclidean_algorithm): \n", "\n", "$$\\operatorname{gcd}(a,b)=\\begin{cases}a & b=0\\\\ \\operatorname{gcd}(b, a\\operatorname{mod}b) & \\text{otherwise.} \\end{cases}$$" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:46.711458Z", "start_time": "2020-10-31T02:43:46.694334Z" }, "nbgrader": { "grade": false, "grade_id": "gcd", "locked": false, "schema_version": 3, "solution": true, "task": false }, "slideshow": { "slide_type": "-" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "5\n" ] }, { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%mytutor -r -h 550\n", "def gcd(a, b):\n", " ### BEGIN SOLUTION\n", " return gcd(b, a % b) if b else a\n", " ### END SOLUTION\n", "\n", "\n", "print(gcd(3 * 5, 5 * 7)) # gcd = ?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "**Is recursion strictly necessary?** " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "E.g., the following computes the Fibonnacci number using a while loop instead." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:47.362797Z", "start_time": "2020-10-31T02:43:47.353483Z" }, "slideshow": { "slide_type": "-" } }, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" }, { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%mytutor -r -h 550\n", "def fibonacci_iteration(n):\n", " if n > 1:\n", " _, F = 0, 1 # next two Fibonacci numbers\n", " while n > 1:\n", " _, F, n = F, F + _, n - 1\n", " return F\n", " elif n == 1:\n", " return 1\n", " else:\n", " return 0\n", "\n", "\n", "fibonacci_iteration(3)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:47.372595Z", "start_time": "2020-10-31T02:43:47.364331Z" } }, "outputs": [], "source": [ "# more tests\n", "for n in range(5):\n", " assert fibonacci(n) == fibonacci_iteration(n)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "**Exercise** We can always convert a recursion to an iteration. Why?\n", "\n", "*Hint:* See the [Wikipedia](https://en.wikipedia.org/wiki/Recursion_(computer_science)#Recursion_versus_iteration)." ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": true, "grade_id": "recursion-vs-iteration", "locked": false, "points": 0, "schema_version": 3, "solution": true, "task": false }, "slideshow": { "slide_type": "-" } }, "source": [ "The step-by-step execution of the recursion is indeed how python implements a recursion as an iteration." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "**Exercise** Implement `gcd_iteration` using a while loop instead of a recursion.\n", "\n", "*Hint:* See [tail recursion](https://en.wikipedia.org/wiki/Recursion_(computer_science)#Tail-recursive_functions)." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:47.618077Z", "start_time": "2020-10-31T02:43:47.608529Z" }, "code_folding": [], "nbgrader": { "grade": false, "grade_id": "gcd_iteration", "locked": false, "schema_version": 3, "solution": true, "task": false }, "slideshow": { "slide_type": "-" } }, "outputs": [ { "data": { "text/plain": [ "5" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" }, { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%mytutor -r -h 550\n", "def gcd_iteration(a, b):\n", " ### BEGIN SOLUTION\n", " while b:\n", " a, b = b, a % b\n", " return a\n", " ### END SOLUTION\n", "\n", "\n", "gcd_iteration(3 * 5, 5 * 7)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:47.626870Z", "start_time": "2020-10-31T02:43:47.619853Z" }, "slideshow": { "slide_type": "fragment" }, "tags": [] }, "outputs": [], "source": [ "# test\n", "for n in range(5):\n", " assert fibonacci(n) == fibonacci_iteration(n)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "**What are the benefits of recursion?**" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "- Recursion is often shorter and easier to understand.\n", "- It is also easier to write code by *wishful thinking* or *[declarative programming](https://en.wikipedia.org/wiki/Declarative_programming)* as supposed to [imperative programming](https://en.wikipedia.org/wiki/Imperative_programming)." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "**Is recusion more efficient than iteration?**" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "**Exercise** Find the smallest values of `n` for `fibonacci(n)` and `fibonacci_iteration(n)` respectively to run for more than a second." ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:49.568032Z", "start_time": "2020-10-31T02:43:48.577448Z" }, "nbgrader": { "grade": false, "grade_id": "fib_recursion", "locked": false, "schema_version": 3, "solution": true, "task": false }, "slideshow": { "slide_type": "fragment" }, "tags": [ "remove-output" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1.55 s ± 49.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" ] } ], "source": [ "%%timeit -n 1\n", "# Assign n the appropriate value\n", "### BEGIN SOLUTION\n", "n = 34\n", "### END SOLUTION\n", "fib_recursion = fibonacci(n)" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:50.313458Z", "start_time": "2020-10-31T02:43:49.569918Z" }, "nbgrader": { "grade": false, "grade_id": "fib_iteration", "locked": false, "schema_version": 3, "solution": true, "task": false }, "slideshow": { "slide_type": "fragment" }, "tags": [ "remove-output" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1.28 s ± 10.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" ] } ], "source": [ "%%timeit -n 1\n", "# Assign n\n", "### BEGIN SOLUTION\n", "n = 400000\n", "### END SOLUTION\n", "fib_iteration = fibonacci_iteration(n)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "To see why recursion is often slower, we will modify `fibonacci` to print each function call as follows." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:50.324561Z", "start_time": "2020-10-31T02:43:50.315516Z" }, "slideshow": { "slide_type": "-" }, "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "fibonacci(5)\n", "fibonacci(4)\n", "fibonacci(3)\n", "fibonacci(2)\n", "fibonacci(1)\n", "fibonacci(0)\n", "fibonacci(1)\n", "fibonacci(2)\n", "fibonacci(1)\n", "fibonacci(0)\n", "fibonacci(3)\n", "fibonacci(2)\n", "fibonacci(1)\n", "fibonacci(0)\n", "fibonacci(1)\n" ] }, { "data": { "text/plain": [ "5" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def fibonacci(n):\n", " \"\"\"Returns the Fibonacci number of order n.\"\"\"\n", " print(f\"fibonacci({n})\")\n", " return fibonacci(n - 1) + fibonacci(n - 2) if n > 1 else 1 if n == 1 else 0\n", "\n", "\n", "fibonacci(5)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "- `fibonacci(5)` calls `fibonacci(4)` and `fibonacci(3)`.\n", "- `fibonacci(4)` then calls `fibonacci(3)` and `fibonacci(2)`.\n", "- `fibonacci(3)` is called twice." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Global Variables and Closures" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Consider generating a sequence of Fibonacci numbers:" ] }, { "cell_type": "code", "execution_count": 61, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:50.334349Z", "start_time": "2020-10-31T02:43:50.326085Z" }, "slideshow": { "slide_type": "-" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "1\n", "2\n", "3\n" ] } ], "source": [ "for n in range(5):\n", " print(fibonacci_iteration(n))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "**Exercise** Is the above loop efficient?" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": true, "grade_id": "fibonacci_sequence", "locked": false, "points": 0, "schema_version": 3, "solution": true, "task": false }, "slideshow": { "slide_type": "fragment" } }, "source": [ "No. Each call to `fibonacci_iteration(n)` recomputes the last two Fibonacci numbers $F_{n-1}$ and $F_{n-2}$ for $n\\geq 2$." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "**How to avoid redundant computations?**" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "One way is to store the last two computed Fibonacci numbers." ] }, { "cell_type": "code", "execution_count": 66, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:50.712718Z", "start_time": "2020-10-31T02:43:50.705710Z" }, "code_folding": [], "slideshow": { "slide_type": "-" }, "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "1\n", "2\n", "3\n", "Global states:\n", " n : Next Fibonacci number = 5\n", " Fnn : Next next Fibonacci number = 8\n", " n : Next order = 5\n" ] }, { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%mytutor -r -h 600\n", "Fn, Fnn, n = 0, 1, 0 # global variables\n", "\n", "\n", "def print_fibonacci_state():\n", " print(\n", " f\"\"\"Global states:\n", " Fn : Next Fibonacci number = {Fn}\n", " Fnn : Next next Fibonacci number = {Fnn}\n", " n : Next order = {n}\"\"\"\n", " )\n", "\n", "\n", "def next_fibonacci():\n", " \"\"\"Returns the next Fibonacci number.\"\"\"\n", " global Fn, Fnn, n # global declaration\n", " value, Fn, Fnn, n = Fn, Fnn, Fn + Fnn, n + 1\n", " return value\n", "\n", "\n", "for i in range(5):\n", " print(next_fibonacci())\n", "print_fibonacci_state()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Rules for [*global/local variables*](https://docs.python.org/3/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python):\n", "1. A local variable must be defined within a function.\n", "1. An assignment defines a local variable except after a [`global` statement](https://docs.python.org/3/reference/simple_stmts.html#the-global-statement)." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "**Why `global` is NOT needed in `print_fibonacci_state`?**" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Without ambiguity, `Fn, Fnn, n` in `print_fibonacci_state` are not local variables by Rule 1 because they are not defined within the function." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "**Why `global` is needed in `next_fibonacci`?**" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "What happens otherwise:" ] }, { "cell_type": "code", "execution_count": 65, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:52.064075Z", "start_time": "2020-10-31T02:43:52.022388Z" }, "slideshow": { "slide_type": "-" }, "tags": [] }, "outputs": [ { "ename": "UnboundLocalError", "evalue": "local variable 'n' referenced before assignment", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mUnboundLocalError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m/tmp/ipykernel_4023/717954880.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 8\u001b[0;31m \u001b[0mnext_fibonacci\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;32m/tmp/ipykernel_4023/717954880.py\u001b[0m in \u001b[0;36mnext_fibonacci\u001b[0;34m()\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m'''Returns the next Fibonacci number.'''\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;31m# global Fn, Fnn, n\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0mvalue\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mn\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 5\u001b[0m \u001b[0mn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mFnn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mFnn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mFnn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mUnboundLocalError\u001b[0m: local variable 'n' referenced before assignment" ] } ], "source": [ "def next_fibonacci():\n", " \"\"\"Returns the next Fibonacci number.\"\"\"\n", " # global Fn, Fnn, n\n", " value = n\n", " n, Fnn, n = Fnn, n + Fnn, n + 1\n", " return value\n", "\n", "\n", "next_fibonacci()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Why is there an `UnboundLocalError`?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "- The assignment defines `n` as a local variable by Rule 2. \n", "- However, the assignment requires first evaluating `n`, which is not yet defined." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "**Are global variables preferred over local ones?**" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Consider rewriting the for loop as a while loop:" ] }, { "cell_type": "code", "execution_count": 70, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:53.266823Z", "start_time": "2020-10-31T02:43:53.262288Z" }, "code_folding": [], "slideshow": { "slide_type": "-" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%mytutor -h 600\n", "Fn, Fnn, n = 0, 1, 0 # global variables\n", "\n", "\n", "def print_fibonacci_state():\n", " print(\n", " f\"\"\"Global states:\n", " Fn : Next Fibonacci number = {Fn}\n", " Fnn : Next next Fibonacci number = {Fnn}\n", " n : Next order = {n}\"\"\"\n", " )\n", "\n", "\n", "def next_fibonacci():\n", " \"\"\"Returns the next Fibonacci number.\"\"\"\n", " global Fn, Fnn, n # global declaration\n", " value, Fn, Fnn, n = Fn, Fnn, Fn + Fnn, n + 1\n", " return value\n", "\n", "\n", "n = 0\n", "while n < 5:\n", " print(next_fibonacci())\n", " n += 1\n", "print_fibonacci_state()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "**Exercise** Why does the while loop prints only 3 numbers instead of 5 Fibonacci numbers?" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": true, "grade_id": "global_bug", "locked": false, "points": 0, "schema_version": 3, "solution": true, "task": false }, "slideshow": { "slide_type": "-" } }, "source": [ "There is a name collision. `n` is also incremented by `next_fibonacci()`, and so the while loop is only executed 3 times in total. " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "To avoid such error, a convention in python is use a leading underscore for variable names that are [*private*](https://www.python.org/dev/peps/pep-0008) (for internal use): \n", "> _single_leading_underscore: weak \"internal use\" indicator. E.g. from M import * does not import objects whose names start with an underscore." ] }, { "cell_type": "code", "execution_count": 72, "metadata": { "slideshow": { "slide_type": "-" } }, "outputs": [ { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%mytutor -h 600\n", "_Fn, _Fnn, _n = 0, 1, 0 # global variables\n", "\n", "\n", "def print_fibonacci_state():\n", " print(\n", " f\"\"\"Global states:\n", " _Fn : Next Fibonacci number = {_Fn}\n", " _Fnn : Next next Fibonacci number = {_Fnn}\n", " _n : Next order = {_n}\"\"\"\n", " )\n", "\n", "\n", "def next_fibonacci():\n", " \"\"\"Returns the next Fibonacci number.\"\"\"\n", " global _Fn, _Fnn, _n # global declaration\n", " value, _Fn, _Fnn, _n = _Fn, _Fnn, _Fn + _Fnn, _n + 1\n", " return value\n", "\n", "\n", "n = 0\n", "while n < 5:\n", " print(next_fibonacci())\n", " n += 1\n", "print_fibonacci_state()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "With global variables:\n", "- codes are less predictable, more difficult to reuse/extend, and\n", "- tests cannot be isolated, making debugging difficult." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "**Is it possible to store the function states without using global variables?**" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "We can use nested functions and [`nonlocal` variables](https://docs.python.org/3/reference/simple_stmts.html#grammar-token-nonlocal-stmt)." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:54.554619Z", "start_time": "2020-10-31T02:43:54.548561Z" }, "code_folding": [], "slideshow": { "slide_type": "-" }, "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "1\n", "2\n", "3\n", "States:\n", " Next Fibonacci number = 5\n", " Next next Fibonacci number = 8\n", " Next order = 5\n" ] } ], "source": [ "def fibonacci_sequence(Fn, Fnn):\n", " def next_fibonacci():\n", " \"\"\"Returns the next (generalized) Fibonacci number starting with\n", " Fn and Fnn as the first two numbers.\"\"\"\n", " nonlocal Fn, Fnn, n # declare nonlocal variables\n", " value = Fn\n", " Fn, Fnn, n = Fnn, Fn + Fnn, n + 1\n", " return value\n", "\n", " def print_fibonacci_state():\n", " print(\n", " \"\"\"States:\n", " Next Fibonacci number = {}\n", " Next next Fibonacci number = {}\n", " Next order = {}\"\"\".format(\n", " Fn, Fnn, n\n", " )\n", " )\n", "\n", " n = 0 # Fn and Fnn specified in the function arguments\n", " return next_fibonacci, print_fibonacci_state\n", "\n", "\n", "next_fibonacci, print_fibonacci_state = fibonacci_sequence(0, 1)\n", "n = 0\n", "while n < 5:\n", " print(next_fibonacci())\n", " n += 1\n", "print_fibonacci_state()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The state variables `Fn, Fnn, n` are now [*encapsulated*](https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)), and the functions returned by `fibonacci_sequence` no longer depends on any global variables." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Another benefit of using nested functions is that we can also create different Fibonacci sequence with different base cases." ] }, { "cell_type": "code", "execution_count": 75, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:55.098142Z", "start_time": "2020-10-31T02:43:55.091975Z" }, "slideshow": { "slide_type": "-" }, "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "cs\n", "1302\n", "cs1302\n", "1302cs1302\n", "cs13021302cs1302\n", "States:\n", " Next Fibonacci number = 1302cs1302cs13021302cs1302\n", " Next next Fibonacci number = cs13021302cs13021302cs1302cs13021302cs1302\n", " Next order = 5\n" ] } ], "source": [ "my_next_fibonacci, my_print_fibonacci_state = fibonacci_sequence(\"cs\", \"1302\")\n", "for n in range(5):\n", " print(my_next_fibonacci())\n", "my_print_fibonacci_state()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "`next_fibonacci` and `print_fibonacci_state` are *local functions* of `fibonacci_sequence`. \n", "- They can access (*capture*) the other local variables of `fibonacci_sequence` by forming the so-called *closures*.\n", "- Similar to the use of `global` statement, a [`non-local` statement](https://docs.python.org/3/reference/simple_stmts.html#the-nonlocal-statement) is needed for assigning nonlocal variables." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Each local function has an attribute named `__closure__` that stores the captured local variables." ] }, { "cell_type": "code", "execution_count": 76, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:55.587635Z", "start_time": "2020-10-31T02:43:55.580308Z" }, "slideshow": { "slide_type": "-" }, "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "closure of next_fibonacci\n", " content: 5\n", " content: 8\n", " content: 5\n", "closure of print_fibonacci_state\n", " content: 5\n", " content: 8\n", " content: 5\n" ] } ], "source": [ "def print_closure(f):\n", " \"\"\"Print the closure of a function.\"\"\"\n", " print(\"closure of \", f.__name__)\n", " for cell in f.__closure__:\n", " print(\" {} content: {!r}\".format(cell, cell.cell_contents))\n", "\n", "\n", "print_closure(next_fibonacci)\n", "print_closure(print_fibonacci_state)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Generator" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "An easier way to generate a sequence of objects one-by-one is to write a *generator*." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:56.056953Z", "start_time": "2020-10-31T02:43:56.053130Z" }, "code_folding": [], "slideshow": { "slide_type": "-" }, "tags": [] }, "outputs": [ { "data": { "text/plain": [ " at 0x7ff3980dd510>" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fibonacci_generator = (fibonacci_iteration(n) for n in range(3))\n", "fibonacci_generator" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The above uses a [*generator expression*](https://docs.python.org/3/reference/expressions.html#grammar-token-generator-expression) to define `fibonacci_generator`." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "**How to obtain items from a generator?**" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "We can use the [`next` function](https://docs.python.org/3/library/functions.html#next)." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:56.941808Z", "start_time": "2020-10-31T02:43:56.933889Z" }, "slideshow": { "slide_type": "-" } }, "outputs": [ { "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/tmp/ipykernel_4269/1490110649.py\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[0mfibonacci_generator\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# raises StopIterationException eventually\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(fibonacci_generator)) # raises StopIterationException eventually" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "A generator object is [*iterable*](https://www.programiz.com/python-programming/iterator), i.e., it implements both `__iter__` and `__next__` methods that are automatically called in a `for` loop as well as the `next` function." ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:57.261618Z", "start_time": "2020-10-31T02:43:57.256104Z" }, "slideshow": { "slide_type": "-" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "1\n", "2\n", "3\n" ] } ], "source": [ "fibonacci_generator = (fibonacci_iteration(n) for n in range(5))\n", "for fib in fibonacci_generator: # StopIterationException handled by for loop\n", " print(fib)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "**Is `fibonacci_generator` efficient?**" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "No, again due to redundant computations. A better way to define the generator is to use the keyword [`yield`](https://docs.python.org/3/reference/expressions.html?highlight=yield#yield-expressions):" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:57.716343Z", "start_time": "2020-10-31T02:43:57.711570Z" }, "slideshow": { "slide_type": "-" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%mytutor -h 450\n", "def fibonacci_sequence(Fn, Fnn, stop):\n", " \"\"\"Return a generator that generates Fibonacci numbers\n", " starting from Fn and Fnn until stop (exclusive).\"\"\"\n", " while Fn < stop:\n", " yield Fn # return Fn and pause execution\n", " Fn, Fnn = Fnn, Fnn + Fn\n", "\n", "\n", "for fib in fibonacci_sequence(0, 1, 5):\n", " print(fib)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "1. `yield` causes the function to return a *generator* without executing the function body.\n", "1. Calling `__next__` resumes the execution, which \n", " - pauses at the next `yield` expression, or\n", " - raises the `StopIterationException` at the end." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "**Exercise** `yield` can be both a statement and an expression. As an expression: \n", "- The value of a `yield` expression is `None` by default, but \n", "- it can be set by the `generator.send` method.\n", "\n", "Add the document string to the following function. In particular, explain the effect of calling the method `send` on the returned generator." ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:58.225871Z", "start_time": "2020-10-31T02:43:58.216334Z" }, "code_folding": [], "nbgrader": { "grade": false, "grade_id": "send", "locked": false, "schema_version": 3, "solution": true, "task": false }, "slideshow": { "slide_type": "-" }, "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "2\n", "2\n", "4\n" ] } ], "source": [ "def fibonacci_sequence(Fn, Fnn, stop):\n", " ### BEGIN SOLUTION\n", " \"\"\"Return a generator that generates Fibonacci numbers\n", " starting from Fn and Fnn to stop (exclusive).\n", " generator.send(value) sets and returns the next number as value.\"\"\"\n", " ### END SOLUTION\n", " while Fn < stop:\n", " value = yield Fn\n", " if value is not None:\n", " Fnn = value # set next number to the value of yield expression\n", " Fn, Fnn = Fnn, Fnn + Fn\n", "\n", "\n", "fibonacci_generator = fibonacci_sequence(0, 1, 5)\n", "print(next(fibonacci_generator))\n", "print(fibonacci_generator.send(2))\n", "for fib in fibonacci_generator:\n", " print(fib)" ] } ], "metadata": { "celltoolbar": "Create Assignment", "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.8.12" }, "latex_envs": { "LaTeX_envs_menu_present": true, "autoclose": false, "autocomplete": true, "bibliofile": "biblio.bib", "cite_by": "apalike", "current_citInitial": 1, "eqLabelWithNumbers": true, "eqNumInitial": 1, "hotkeys": { "equation": "Ctrl-E", "itemize": "Ctrl-I" }, "labels_anchors": false, "latex_user_defs": false, "report_style_numbering": false, "user_envs_cfg": false }, "rise": { "enable_chalkboard": true, "scroll": true, "theme": "white" }, "toc": { "base_numbering": 1, "nav_menu": { "height": "195px", "width": "330px" }, "number_sections": true, "sideBar": true, "skip_h1_title": true, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": { "height": "454.418px", "left": "1533px", "top": "110.284px", "width": "261px" }, "toc_section_display": true, "toc_window_display": false }, "widgets": { "application/vnd.jupyter.widget-state+json": { "state": {}, "version_major": 2, "version_minor": 0 } } }, "nbformat": 4, "nbformat_minor": 4 }