{ "cells": [ { "cell_type": "markdown", "metadata": { "cell_style": "center", "slideshow": { "slide_type": "slide" } }, "source": [ "# Decorator" ] }, { "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": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Optional Arguments" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Recall the generator for Fibonacci numbers:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "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": [ "Fibonacci sequence normally starts with `0` and `1` by default. Is it possible to make arguments `Fn` and `Fnn` optional?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "**How to give arguments default values?**" ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:58.713936Z", "start_time": "2020-10-31T02:43:58.708189Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "def fibonacci_sequence(Fn=0, Fnn=1, stop=None):\n", " while stop is None or Fn < stop:\n", " value = yield Fn\n", " Fn, Fnn = Fnn, Fnn + Fn" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Arguments with default values specified by `=...` are called [default arguments](https://docs.python.org/3/tutorial/controlflow.html#default-argument-values). They are optional in the function call:" ] }, { "cell_type": "code", "execution_count": 36, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:58.738329Z", "start_time": "2020-10-31T02:43:58.728501Z" }, "slideshow": { "slide_type": "-" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "1\n", "2\n", "3\n" ] } ], "source": [ "for fib in fibonacci_sequence(stop=5):\n", " print(fib) # with default Fn=0, Fnn=1" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "`stop=5` in the function call is called a [keyword argument](https://docs.python.org/3/glossary.html#term-keyword-argument). As supposed to [*positional arguments*](https://docs.python.org/3/glossary.html#term-argument), it specifies the name of the argument explicitly." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "**Exercise** Is `fibonacci_sequence(stop=5)` the same as `fibonacci_sequence(5)`? In particular, what is the behavior of the following code?" ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:43:59.297944Z", "start_time": "2020-10-31T02:43:59.292358Z" }, "slideshow": { "slide_type": "-" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "5\n", "1\n", "6\n", "7\n", "13\n" ] } ], "source": [ "for fib in fibonacci_sequence(5):\n", " print(fib)\n", " if fib > 10: \n", " break # Will this be executed?" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": true, "grade_id": "stop", "locked": false, "points": 0, "schema_version": 3, "solution": true, "task": false }, "slideshow": { "slide_type": "-" } }, "source": [ "With `fibonacci_sequence(5)`, `Fn=5` while `Fnn=1` and `stop=None` by default. The while loop in the definition of `fibonacci_sequence` becomes an infinite loop. The generator keeps generating the next Fibonacci number without raising `StopIteration` exception, and so the for loop will be an infinite loop unless it is terminated by the break statement." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Rules for specifying arguments: \n", "1. Default (Keyword) arguments must be after all non-default (positional) arguments in a function definition (call).\n", "1. The value for an argument can not be specified more than once in a function definition (call)." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "E.g., the following results in an error:" ] }, { "cell_type": "code", "execution_count": 40, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:00.150820Z", "start_time": "2020-10-31T02:44:00.144212Z" }, "slideshow": { "slide_type": "-" } }, "outputs": [ { "ename": "SyntaxError", "evalue": "positional argument follows keyword argument (2723126621.py, line 1)", "output_type": "error", "traceback": [ "\u001b[0;36m File \u001b[0;32m\"/tmp/ipykernel_4269/2723126621.py\"\u001b[0;36m, line \u001b[0;32m1\u001b[0m\n\u001b[0;31m fibonacci_sequence(stop=10, 1)\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m positional argument follows keyword argument\n" ] } ], "source": [ "fibonacci_sequence(stop=10, 1)" ] }, { "cell_type": "code", "execution_count": 41, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:00.153925Z", "start_time": "2020-10-31T02:44:00.040Z" }, "slideshow": { "slide_type": "-" } }, "outputs": [ { "ename": "TypeError", "evalue": "fibonacci_sequence() got multiple values for argument 'Fn'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m/tmp/ipykernel_4269/3954571811.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mfibonacci_sequence\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mFn\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: fibonacci_sequence() got multiple values for argument 'Fn'" ] } ], "source": [ "fibonacci_sequence(1, Fn=1)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "The following shows that the behavior of `range` is different." ] }, { "cell_type": "code", "execution_count": 42, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:00.435983Z", "start_time": "2020-10-31T02:44:00.423137Z" }, "scrolled": true, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 3 5 7 9 \n", "1 2 3 4 5 6 7 8 9 \n", "0 1 2 3 4 5 6 7 8 9 " ] }, { "ename": "TypeError", "evalue": "range() takes no keyword arguments", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m/tmp/ipykernel_4269/1295519871.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mcount\u001b[0m \u001b[0;32min\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;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcount\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mend\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m' '\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# default start=0, step=1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 9\u001b[0;31m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstop\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m10\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# fails\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: range() takes no keyword arguments" ] } ], "source": [ "for count in range(1, 10, 2):\n", " print(count, end=\" \") # counts from 1 to 10 in steps of 2\n", "print()\n", "for count in range(1, 10):\n", " print(count, end=\" \") # default step=1\n", "print()\n", "for count in range(10):\n", " print(count, end=\" \") # default start=0, step=1\n", "range(stop=10) # fails" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "`range` takes only positional arguments. \n", "However, the first positional argument has different intepretations (`start` or `stop`) depending on the number of arguments (2 or 1)." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "`range` is indeed NOT a generator." ] }, { "cell_type": "code", "execution_count": 43, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:00.927005Z", "start_time": "2020-10-31T02:44:00.921731Z" }, "collapsed": true, "jupyter": { "outputs_hidden": true }, "scrolled": true, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " \n" ] } ], "source": [ "print(type(range), type(range(10)))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" }, "tags": [] }, "source": [ "**How is range implemented?**" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Variable number of arguments" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "[The implementation of range](https://github.com/python/cpython/blob/6afb285ff0790471a6858e44f85d143f07fda70c/Objects/rangeobject.c#L82-L123) uses a [variable number of arguments](https://docs.python.org/3.4/tutorial/controlflow.html#arbitrary-argument-lists)." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:01.642620Z", "start_time": "2020-10-31T02:44:01.634575Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "args (): (0, 10, 2)\n", "kwargs (): {'start': 1, 'stop': 2}\n" ] } ], "source": [ "def print_arguments(*args, **kwargs):\n", " \"\"\"Take any number of arguments and prints them\"\"\"\n", " print(\"args ({}): {}\".format(type(args), args))\n", " print(\"kwargs ({}): {}\".format(type(kwargs), kwargs))\n", "\n", "\n", "print_arguments(0, 10, 2, start=1, stop=2)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "- `args` is a tuple of positional arguments.\n", "- `kwargs` is a dictionary of keyword arguments." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "`*` and `**` are *unpacking operators* for tuple/list and dictionary respectively:" ] }, { "cell_type": "code", "execution_count": 45, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:02.336627Z", "start_time": "2020-10-31T02:44:02.332934Z" }, "scrolled": true, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "args (): (0, 10, 2)\n", "kwargs (): {'start': 1, 'stop': 2}\n" ] } ], "source": [ "args = (0, 10, 2)\n", "kwargs = {\"start\": 1, \"stop\": 2}\n", "print_arguments(*args, **kwargs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following function converts all the arguments to a string. \n", "It will be useful later on." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:02.594868Z", "start_time": "2020-10-31T02:44:02.584464Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "'(0, 10, 2, start=1, stop=2)'" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def argument_string(*args, **kwargs):\n", " \"\"\"Return the string representation of the list of arguments.\"\"\"\n", " return \"({})\".format(\n", " \", \".join(\n", " [\n", " *[\"{!r}\".format(v) for v in args], # arguments\n", " *[\n", " \"{}={!r}\".format(k, v) for k, v in kwargs.items()\n", " ], # keyword arguments\n", " ]\n", " )\n", " )\n", "\n", "\n", "argument_string(0, 10, 2, start=1, stop=2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Exercise** Why use `\"{!r}\".format(v)` to format the argument?\n", "\n", "*Hint:* See [token conversion](https://docs.python.org/3/reference/lexical_analysis.html#grammar-token-conversion) and the following code:" ] }, { "cell_type": "code", "execution_count": 79, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(\"'a'\", \"'a'\")" ] }, "execution_count": 79, "metadata": {}, "output_type": "execute_result" } ], "source": [ "v = 'a'\n", "\"{!r}\".format(v), repr(v)" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": true, "grade_id": "repr", "locked": false, "points": 0, "schema_version": 3, "solution": true, "task": false } }, "source": [ "`!r` will convert $v$ to the string representation that can be evaluated by python `eval`. In particular, `'a'` will be converted to `\"'a'\"`, which has the quotation needed for the string literal. " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "**Exercise** Redefine `fibonacci_sequence` so that the positional arguments depend on the number of arguments:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:02.842063Z", "start_time": "2020-10-31T02:44:02.826424Z" }, "code_folding": [ 19 ], "nbgrader": { "grade": false, "grade_id": "optional", "locked": false, "schema_version": 3, "solution": true, "task": false }, "slideshow": { "slide_type": "-" }, "tags": [ "remove-output" ] }, "outputs": [], "source": [ "def fibonacci_sequence(*args):\n", " \"\"\"Return a generator that generates Fibonacci numbers\n", " starting from Fn and Fnn to stop (exclusive).\n", " generator.send(value) sets next number to value.\n", "\n", " fibonacci_sequence(stop)\n", " fibonacci_sequence(Fn,Fnn)\n", " fibonacci_sequence(Fn,Fnn,stop)\n", " \"\"\"\n", " Fn, Fnn, stop = 0, 1, None # default values\n", "\n", " # handle different number of arguments\n", " if len(args) == 1:\n", " ### BEGIN SOLUTION\n", " stop = args[0]\n", " ### END SOLUTION\n", " elif len(args) == 2:\n", " Fn, Fnn = args[0], args[1]\n", " elif len(args) > 2:\n", " Fn, Fnn, stop = args[0], args[1], args[2]\n", "\n", " while stop is None or 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" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:02.849615Z", "start_time": "2020-10-31T02:44:02.843656Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "1\n", "2\n", "3\n" ] } ], "source": [ "for fib in fibonacci_sequence(5): # default Fn=0, Fn=1\n", " print(fib)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:02.860976Z", "start_time": "2020-10-31T02:44:02.851994Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "2\n", "3\n", "5\n", "8\n" ] } ], "source": [ "for fib in fibonacci_sequence(1, 2): # default stop=None\n", " print(fib)\n", " if fib > 5:\n", " break" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:02.872022Z", "start_time": "2020-10-31T02:44:02.862892Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "2\n", "3\n" ] } ], "source": [ "args = (1, 2, 5)\n", "for fib in fibonacci_sequence(*args): # default stop=None\n", " print(fib)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Decorator" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "**What is function decoration?** \n", "**Why decorate a function?**" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:03.368911Z", "start_time": "2020-10-31T02:44:03.348824Z" }, "slideshow": { "slide_type": "-" }, "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " 1: fibonacci(0)\n", "Done\n", "0\n", " 1: fibonacci(1)\n", "Done\n", "1\n", " 1: fibonacci(2)\n", " 2: |fibonacci(1)\n", " 3: |fibonacci(0)\n", "Done\n", "1\n", " 1: fibonacci(3)\n", " 2: |fibonacci(2)\n", " 3: ||fibonacci(1)\n", " 4: ||fibonacci(0)\n", " 5: |fibonacci(1)\n", "Done\n", "2\n", " 1: fibonacci(4)\n", " 2: |fibonacci(3)\n", " 3: ||fibonacci(2)\n", " 4: |||fibonacci(1)\n", " 5: |||fibonacci(0)\n", " 6: ||fibonacci(1)\n", " 7: |fibonacci(2)\n", " 8: ||fibonacci(1)\n", " 9: ||fibonacci(0)\n", "Done\n", "3\n", " 1: fibonacci(5)\n", " 2: |fibonacci(4)\n", " 3: ||fibonacci(3)\n", " 4: |||fibonacci(2)\n", " 5: ||||fibonacci(1)\n", " 6: ||||fibonacci(0)\n", " 7: |||fibonacci(1)\n", " 8: ||fibonacci(2)\n", " 9: |||fibonacci(1)\n", " 10: |||fibonacci(0)\n", " 11: |fibonacci(3)\n", " 12: ||fibonacci(2)\n", " 13: |||fibonacci(1)\n", " 14: |||fibonacci(0)\n", " 15: ||fibonacci(1)\n", "Done\n", "5\n" ] } ], "source": [ "def fibonacci(n):\n", " \"\"\"Returns the Fibonacci number of order n.\"\"\"\n", " global count, depth\n", " count += 1\n", " depth += 1\n", " print(\"{:>3}: {}fibonacci({!r})\".format(count, \"|\" * depth, n))\n", "\n", " value = fibonacci(n - 1) + fibonacci(n - 2) if n > 1 else 1 if n == 1 else 0\n", "\n", " depth -= 1\n", " if depth == -1: # recursion done\n", " print(\"Done\")\n", " count = 0 # reset count for subsequent recursions\n", " return value\n", "\n", "\n", "count, depth = 0, -1\n", "for n in range(6):\n", " print(fibonacci(n))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The code decorates the `fibonacci` function by printing each recursive call and the depth of the call stack. \n", "The decoration is useful in showing the efficiency of the function, but it rewrites the function definition." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "**How to decorate a function without changing its code?**" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "- What if the decorations are temporary and should be removed later? \n", "- Go through the source codes of all decorated functions to remove the decorations? \n", "- When updating a piece of code, switch back and forth between original and decorated codes?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "What about defining a new function that calls and decorates the original function?" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:04.465212Z", "start_time": "2020-10-31T02:44:04.456584Z" }, "code_folding": [ 0 ], "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " 1: fibonacci(0)\n", "Done\n", "0\n", " 1: fibonacci(1)\n", "Done\n", "1\n", " 1: fibonacci(2)\n", "Done\n", "1\n", " 1: fibonacci(3)\n", "Done\n", "2\n", " 1: fibonacci(4)\n", "Done\n", "3\n", " 1: fibonacci(5)\n", "Done\n", "5\n" ] } ], "source": [ "def fibonacci(n):\n", " \"\"\"Returns the Fibonacci number of order n.\"\"\"\n", " return fibonacci(n - 1) + fibonacci(n - 2) if n > 1 else 1 if n == 1 else 0\n", "\n", "\n", "def fibonacci_decorated(n):\n", " \"\"\"Returns the Fibonacci number of order n.\"\"\"\n", " global count, depth\n", " count += 1\n", " depth += 1\n", " print(\"{:>3}: {}fibonacci({!r})\".format(count, \"|\" * depth, n))\n", "\n", " value = fibonacci(n)\n", "\n", " depth -= 1\n", " if depth == -1: # recursion done\n", " print(\"Done\")\n", " count = 0 # reset count for subsequent recursions\n", " return value\n", "\n", "\n", "count, depth = 0, -1\n", "for n in range(6):\n", " print(fibonacci_decorated(n))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "We want `fibonacci` to call `fibonacci_decorated` instead. \n", "What about renaming `fibonacci_decorated` to `fibonacci`?\n", "\n", "```Python\n", "fibonacci = fibonacci_decorated\n", "count, depth = 0, -1\n", "fibonacci_decorated(10)\n", "```\n", "\n", "(If you are faint-hearted, don't run the above code.)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "We want `fibonacci_decorated` to call the original `fibonacci`." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "The solution is to capture the original `fibonacci` in a closure:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:05.067305Z", "start_time": "2020-10-31T02:44:05.056730Z" }, "code_folding": [], "slideshow": { "slide_type": "fragment" }, "tags": [] }, "outputs": [], "source": [ "import functools\n", "\n", "\n", "def print_function_call(f):\n", " \"\"\"Decorate a recursive function to print the call stack.\n", "\n", " The decorator also keep tracks of the number and depth of a recursive call to print the call stack.\n", "\n", " Parameters\n", " ----------\n", " f: Calla76ble\n", " A recursive function.\n", "\n", " Returns\n", " -------\n", " Callable:j\n", " The decorated function that also prints the function call\n", " when called.\n", " \"\"\"\n", "\n", " @functools.wraps(f) # give wrapper the identity of f and more\n", " def wrapper(*args, **kwargs):\n", " nonlocal count, depth\n", " count += 1\n", " depth += 1\n", " call = \"{}{}\".format(f.__name__, argument_string(*args, **kwargs))\n", " print(\"{:>3}:{}{}\".format(count, \"|\" * depth, call))\n", "\n", " value = f(*args, **kwargs) # calls f\n", "\n", " depth -= 1\n", " if depth == -1:\n", " print(\"Done\")\n", " count = 0\n", " return value\n", "\n", " count, depth = 0, -1\n", " return wrapper # return the decorated function" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "`print_function_call` takes in `f` and returns `wrapper`, which captures and decorates `f`:\n", "- `wrapper` expects the same set of arguments for `f`, \n", "- returns the same value returned by `f` on the arguments, but\n", "- can execute additional codes before and after calling `f` to print the function call." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "By redefining `fibonacci` as the returned `wrapper`, the original `fibonacci` captured by `wrapper` calls `wrapper` as desired." ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:05.613215Z", "start_time": "2020-10-31T02:44:05.603533Z" }, "code_folding": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " 1:fibonacci(5)\n", " 2:|fibonacci(4)\n", " 3:||fibonacci(3)\n", " 4:|||fibonacci(2)\n", " 5:||||fibonacci(1)\n", " 6:||||fibonacci(0)\n", " 7:|||fibonacci(1)\n", " 8:||fibonacci(2)\n", " 9:|||fibonacci(1)\n", " 10:|||fibonacci(0)\n", " 11:|fibonacci(3)\n", " 12:||fibonacci(2)\n", " 13:|||fibonacci(1)\n", " 14:|||fibonacci(0)\n", " 15:||fibonacci(1)\n", "Done\n" ] }, { "data": { "text/plain": [ "5" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def fibonacci(n):\n", " return fibonacci(n - 1) + fibonacci(n - 2) if n > 1 else 1 if n == 1 else 0\n", "\n", "\n", "fibonacci = print_function_call(fibonacci) # so original fibonnacci calls wrapper\n", "fibonacci(5)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The redefinition does not change the original `fibonacci` captured by `wrapper`." ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:05.865764Z", "start_time": "2020-10-31T02:44:05.857623Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "def fibonacci(n):\n", " return fibonacci(n - 1) + fibonacci(n - 2) if n > 1 else 1 if n == 1 else 0\n", "\n" ] } ], "source": [ "import inspect\n", "\n", "for cell in fibonacci.__closure__:\n", " if callable(cell.cell_contents):\n", " print(inspect.getsource(cell.cell_contents))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Python provides the syntatic sugar below to simplify the redefinition." ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:06.147287Z", "start_time": "2020-10-31T02:44:06.133616Z" }, "code_folding": [], "slideshow": { "slide_type": "-" }, "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " 1:fibonacci(5)\n", " 2:|fibonacci(4)\n", " 3:||fibonacci(3)\n", " 4:|||fibonacci(2)\n", " 5:||||fibonacci(1)\n", " 6:||||fibonacci(0)\n", " 7:|||fibonacci(1)\n", " 8:||fibonacci(2)\n", " 9:|||fibonacci(1)\n", " 10:|||fibonacci(0)\n", " 11:|fibonacci(3)\n", " 12:||fibonacci(2)\n", " 13:|||fibonacci(1)\n", " 14:|||fibonacci(0)\n", " 15:||fibonacci(1)\n", "Done\n" ] }, { "data": { "text/plain": [ "5" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "@print_function_call\n", "def 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": "subslide" } }, "source": [ "There are many techniques used in the above decorator." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "**Why use a variable number of arguments in `wrapper`**" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "To decorate any function with possibly different number of arguments." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "**Why decorate the wrapper with `@functools.wraps(f)`?**" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "- Ensures some attributes (such as `__name__`) of the wrapper function is the same as those of `f`.\n", "- Add useful attributes. E.g., `__wrapped__` stores the original function so we can undo the decoration.\n" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:08.151683Z", "start_time": "2020-10-31T02:44:08.140353Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "original fibonacci:\n", "5\n", "decorated fibonacci:\n", " 1:fibonacci(5)\n", " 2:|fibonacci(4)\n", " 3:||fibonacci(3)\n", " 4:|||fibonacci(2)\n", " 5:||||fibonacci(1)\n", " 6:||||fibonacci(0)\n", " 7:|||fibonacci(1)\n", " 8:||fibonacci(2)\n", " 9:|||fibonacci(1)\n", " 10:|||fibonacci(0)\n", " 11:|fibonacci(3)\n", " 12:||fibonacci(2)\n", " 13:|||fibonacci(1)\n", " 14:|||fibonacci(0)\n", " 15:||fibonacci(1)\n", "Done\n", "5\n" ] } ], "source": [ "fibonacci, fibonacci_decorated = fibonacci.__wrapped__, fibonacci # recover\n", "print(\"original fibonacci:\")\n", "print(fibonacci(5))\n", "\n", "fibonacci = fibonacci_decorated # decorate\n", "print(\"decorated fibonacci:\")\n", "print(fibonacci(5))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "**How to use decorator to improve recursion?**" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "We can also use a decorator to make recursion more efficient by caching the return values. \n", "`cache` is a dictionary where `cache[n]` stores the computed value of $F_n$ to avoid redundant computations." ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:08.641679Z", "start_time": "2020-10-31T02:44:08.632945Z" }, "scrolled": true, "slideshow": { "slide_type": "fragment" }, "tags": [] }, "outputs": [], "source": [ "def caching(f):\n", " \"\"\"Cache the return value of a function that takes a single argument.\n", "\n", " Parameters\n", " ----------\n", " f: Callable\n", " A function that takes a single argument.\n", "\n", " Returns\n", " -------\n", " Callable:\n", " The function same as f but has its return valued automatically cached\n", " when called. It has a method clear_cache to clear its cache.\n", " \"\"\"\n", "\n", " @functools.wraps(f)\n", " def wrapper(n):\n", " if n not in cache:\n", " cache[n] = f(n)\n", " else:\n", " print(\"read from cache\")\n", " return cache[n]\n", "\n", " cache = {}\n", " wrapper.clear_cache = lambda: cache.clear() # add method to clear cache\n", " return wrapper\n", "\n", "\n", "@print_function_call\n", "@caching\n", "def fibonacci(n):\n", " return fibonacci(n - 1) + fibonacci(n - 2) if n > 1 else 1 if n == 1 else 0" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:08.660463Z", "start_time": "2020-10-31T02:44:08.643793Z" }, "slideshow": { "slide_type": "fragment" }, "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " 1:fibonacci(5)\n", " 2:|fibonacci(4)\n", " 3:||fibonacci(3)\n", " 4:|||fibonacci(2)\n", " 5:||||fibonacci(1)\n", " 6:||||fibonacci(0)\n", " 7:|||fibonacci(1)\n", "read from cache\n", " 8:||fibonacci(2)\n", "read from cache\n", " 9:|fibonacci(3)\n", "read from cache\n", "Done\n", " 1:fibonacci(5)\n", "read from cache\n", "Done\n", " 1:fibonacci(5)\n", " 2:|fibonacci(4)\n", " 3:||fibonacci(3)\n", " 4:|||fibonacci(2)\n", " 5:||||fibonacci(1)\n", " 6:||||fibonacci(0)\n", " 7:|||fibonacci(1)\n", "read from cache\n", " 8:||fibonacci(2)\n", "read from cache\n", " 9:|fibonacci(3)\n", "read from cache\n", "Done\n" ] }, { "data": { "text/plain": [ "5" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fibonacci(5)\n", "fibonacci(5)\n", "fibonacci.clear_cache()\n", "fibonacci(5)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "A method `clear_cache` is added to the wrapper to clear the cache. \n", "`lambda : `is called a [*lambda* expression](https://docs.python.org/3/reference/expressions.html#lambda), which conveniently defines an *anonymous function*." ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:08.918456Z", "start_time": "2020-10-31T02:44:08.912645Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "(function, '')" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(fibonacci.clear_cache), fibonacci.clear_cache.__name__" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Module" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "**How to create a module?**" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "To create a module, simply put the code in a python source file `.py` in\n", "- the current directory, or\n", "- a python *site-packages* directory in system path." ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:09.980305Z", "start_time": "2020-10-31T02:44:09.975970Z" }, "slideshow": { "slide_type": "-" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['/home/cs1302/21a/source/Lecture6', '/opt/conda/lib/python38.zip', '/opt/conda/lib/python3.8', '/opt/conda/lib/python3.8/lib-dynload', '', '/opt/conda/lib/python3.8/site-packages', '/opt/conda/lib/python3.8/site-packages/IPython/extensions', '/home/cs1302/.ipython']\n" ] } ], "source": [ "import sys\n", "\n", "print(sys.path)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "For example, `recurtools.py` in the current directory defines the module `recurtools`." ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:10.235662Z", "start_time": "2020-10-31T02:44:10.226972Z" }, "slideshow": { "slide_type": "-" }, "tags": [ "output_scroll" ] }, "outputs": [ { "data": { "text/html": [ "
'''\n",
       "Recursion Tools\n",
       "===============\n",
       "\n",
       "Contain tools to print the call stack and cache the return values\n",
       "of a recursive function.\n",
       "'''\n",
       "\n",
       "import functools\n",
       "\n",
       "\n",
       "def _argument_string(*args, **kwargs):\n",
       "    """Return the string representation of the list of arguments."""\n",
       "    return "({})".format(\n",
       "        ", ".join(\n",
       "            [\n",
       "                *["{!r}".format(v) for v in args],  # arguments\n",
       "                *[\n",
       "                    "{}={!r}".format(k, v) for k, v in kwargs.items()\n",
       "                ],  # keyword arguments\n",
       "            ]\n",
       "        )\n",
       "    )\n",
       "\n",
       "\n",
       "def print_function_call(f):\n",
       "    """Decorate a recursive function to print the call stack.\n",
       "\n",
       "    The decorator also keep tracks of the number and depth of a recursive call to print the call stack.\n",
       "\n",
       "    Parameters\n",
       "    ----------\n",
       "    f: Callable\n",
       "        A recursive function.\n",
       "\n",
       "    Returns\n",
       "    -------\n",
       "    Callable:\n",
       "        The decorated function that also prints the function call\n",
       "        when called.\n",
       "\n",
       "    Examples:\n",
       "    ---------\n",
       "    >>> @print_function_call\n",
       "    ... def fibonacci(n):\n",
       "    ...     return fibonacci(n - 1) + fibonacci(n - 2) if n > 1 else 1 if n == 1 else 0\n",
       "    ...\n",
       "    >>> fibonacci(5)\n",
       "      1:fibonacci(5)\n",
       "      2:|fibonacci(4)\n",
       "      3:||fibonacci(3)\n",
       "      4:|||fibonacci(2)\n",
       "      5:||||fibonacci(1)\n",
       "      6:||||fibonacci(0)\n",
       "      7:|||fibonacci(1)\n",
       "      8:||fibonacci(2)\n",
       "      9:|||fibonacci(1)\n",
       "     10:|||fibonacci(0)\n",
       "     11:|fibonacci(3)\n",
       "     12:||fibonacci(2)\n",
       "     13:|||fibonacci(1)\n",
       "     14:|||fibonacci(0)\n",
       "     15:||fibonacci(1)\n",
       "    Done\n",
       "    5\n",
       "    """\n",
       "\n",
       "    @functools.wraps(f)  # give wrapper the identity of f and more\n",
       "    def wrapper(*args, **kwargs):\n",
       "        nonlocal count, depth\n",
       "        count += 1\n",
       "        depth += 1\n",
       "        call = "{}{}".format(f.__name__, _argument_string(*args, **kwargs))\n",
       "        print("{:>3}:{}{}".format(count, "|" * depth, call))\n",
       "\n",
       "        value = f(*args, **kwargs)  # calls f\n",
       "\n",
       "        depth -= 1\n",
       "        if depth == -1:\n",
       "            print("Done")\n",
       "            count = 0\n",
       "        return value\n",
       "\n",
       "    count, depth = 0, -1\n",
       "    return wrapper  # return the decorated function\n",
       "\n",
       "\n",
       "def caching(f):\n",
       "    """Cache the return value of a function that takes a single argument.\n",
       "\n",
       "    Parameters:\n",
       "    -----------\n",
       "    f: Callable\n",
       "        A function that takes a single argument.\n",
       "\n",
       "    Returns:\n",
       "    --------\n",
       "    Callable:\n",
       "        The function same as f but has its return valued automatically cached\n",
       "        when called. It has a method clear_cache to clear its cache.\n",
       "\n",
       "    Examples:\n",
       "    ---------\n",
       "    >>> @print_function_call\n",
       "    ... @caching\n",
       "    ... def fibonacci(n):\n",
       "    ...     return fibonacci(n - 1) + fibonacci(n - 2) if n > 1 else 1 if n == 1 else 0\n",
       "    ...\n",
       "    >>> fibonacci(5)\n",
       "      1:fibonacci(5)\n",
       "      2:|fibonacci(4)\n",
       "      3:||fibonacci(3)\n",
       "      4:|||fibonacci(2)\n",
       "      5:||||fibonacci(1)\n",
       "      6:||||fibonacci(0)\n",
       "      7:|||fibonacci(1)\n",
       "    read from cache\n",
       "      8:||fibonacci(2)\n",
       "    read from cache\n",
       "      9:|fibonacci(3)\n",
       "    read from cache\n",
       "    Done\n",
       "    5\n",
       "    >>> fibonacci(5)\n",
       "      1:fibonacci(5)\n",
       "    read from cache\n",
       "    Done\n",
       "    5\n",
       "    >>> fibonacci.clear_cache()\n",
       "    >>> fibonacci(5)\n",
       "      1:fibonacci(5)\n",
       "      2:|fibonacci(4)\n",
       "      3:||fibonacci(3)\n",
       "      4:|||fibonacci(2)\n",
       "      5:||||fibonacci(1)\n",
       "      6:||||fibonacci(0)\n",
       "      7:|||fibonacci(1)\n",
       "    read from cache\n",
       "      8:||fibonacci(2)\n",
       "    read from cache\n",
       "      9:|fibonacci(3)\n",
       "    read from cache\n",
       "    Done\n",
       "    5\n",
       "    """\n",
       "\n",
       "    @functools.wraps(f)\n",
       "    def wrapper(n):\n",
       "        if n not in cache:\n",
       "            cache[n] = f(n)\n",
       "        else:\n",
       "            print("read from cache")\n",
       "        return cache[n]\n",
       "\n",
       "    cache = {}\n",
       "    wrapper.clear_cache = lambda: cache.clear()  # add method to clear cache\n",
       "    return wrapper\n",
       "
\n" ], "text/latex": [ "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", "\\PY{l+s+sd}{\\PYZsq{}\\PYZsq{}\\PYZsq{}}\n", "\\PY{l+s+sd}{Recursion Tools}\n", "\\PY{l+s+sd}{===============}\n", "\n", "\\PY{l+s+sd}{Contain tools to print the call stack and cache the return values}\n", "\\PY{l+s+sd}{of a recursive function.}\n", "\\PY{l+s+sd}{\\PYZsq{}\\PYZsq{}\\PYZsq{}}\n", "\n", "\\PY{k+kn}{import} \\PY{n+nn}{functools}\n", "\n", "\n", "\\PY{k}{def} \\PY{n+nf}{\\PYZus{}argument\\PYZus{}string}\\PY{p}{(}\\PY{o}{*}\\PY{n}{args}\\PY{p}{,} \\PY{o}{*}\\PY{o}{*}\\PY{n}{kwargs}\\PY{p}{)}\\PY{p}{:}\n", " \\PY{l+s+sd}{\\PYZdq{}\\PYZdq{}\\PYZdq{}Return the string representation of the list of arguments.\\PYZdq{}\\PYZdq{}\\PYZdq{}}\n", " \\PY{k}{return} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{(}\\PY{l+s+si}{\\PYZob{}\\PYZcb{}}\\PY{l+s+s2}{)}\\PY{l+s+s2}{\\PYZdq{}}\\PY{o}{.}\\PY{n}{format}\\PY{p}{(}\n", " \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{, }\\PY{l+s+s2}{\\PYZdq{}}\\PY{o}{.}\\PY{n}{join}\\PY{p}{(}\n", " \\PY{p}{[}\n", " \\PY{o}{*}\\PY{p}{[}\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+si}{\\PYZob{}!r\\PYZcb{}}\\PY{l+s+s2}{\\PYZdq{}}\\PY{o}{.}\\PY{n}{format}\\PY{p}{(}\\PY{n}{v}\\PY{p}{)} \\PY{k}{for} \\PY{n}{v} \\PY{o+ow}{in} \\PY{n}{args}\\PY{p}{]}\\PY{p}{,} \\PY{c+c1}{\\PYZsh{} arguments}\n", " \\PY{o}{*}\\PY{p}{[}\n", " \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+si}{\\PYZob{}\\PYZcb{}}\\PY{l+s+s2}{=}\\PY{l+s+si}{\\PYZob{}!r\\PYZcb{}}\\PY{l+s+s2}{\\PYZdq{}}\\PY{o}{.}\\PY{n}{format}\\PY{p}{(}\\PY{n}{k}\\PY{p}{,} \\PY{n}{v}\\PY{p}{)} \\PY{k}{for} \\PY{n}{k}\\PY{p}{,} \\PY{n}{v} \\PY{o+ow}{in} \\PY{n}{kwargs}\\PY{o}{.}\\PY{n}{items}\\PY{p}{(}\\PY{p}{)}\n", " \\PY{p}{]}\\PY{p}{,} \\PY{c+c1}{\\PYZsh{} keyword arguments}\n", " \\PY{p}{]}\n", " \\PY{p}{)}\n", " \\PY{p}{)}\n", "\n", "\n", "\\PY{k}{def} \\PY{n+nf}{print\\PYZus{}function\\PYZus{}call}\\PY{p}{(}\\PY{n}{f}\\PY{p}{)}\\PY{p}{:}\n", " \\PY{l+s+sd}{\\PYZdq{}\\PYZdq{}\\PYZdq{}Decorate a recursive function to print the call stack.}\n", "\n", "\\PY{l+s+sd}{ The decorator also keep tracks of the number and depth of a recursive call to print the call stack.}\n", "\n", "\\PY{l+s+sd}{ Parameters}\n", "\\PY{l+s+sd}{ \\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}}\n", "\\PY{l+s+sd}{ f: Callable}\n", "\\PY{l+s+sd}{ A recursive function.}\n", "\n", "\\PY{l+s+sd}{ Returns}\n", "\\PY{l+s+sd}{ \\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}}\n", "\\PY{l+s+sd}{ Callable:}\n", "\\PY{l+s+sd}{ The decorated function that also prints the function call}\n", "\\PY{l+s+sd}{ when called.}\n", "\n", "\\PY{l+s+sd}{ Examples:}\n", "\\PY{l+s+sd}{ \\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}}\n", "\\PY{l+s+sd}{ \\PYZgt{}\\PYZgt{}\\PYZgt{} @print\\PYZus{}function\\PYZus{}call}\n", "\\PY{l+s+sd}{ ... def fibonacci(n):}\n", "\\PY{l+s+sd}{ ... return fibonacci(n \\PYZhy{} 1) + fibonacci(n \\PYZhy{} 2) if n \\PYZgt{} 1 else 1 if n == 1 else 0}\n", "\\PY{l+s+sd}{ ...}\n", "\\PY{l+s+sd}{ \\PYZgt{}\\PYZgt{}\\PYZgt{} fibonacci(5)}\n", "\\PY{l+s+sd}{ 1:fibonacci(5)}\n", "\\PY{l+s+sd}{ 2:|fibonacci(4)}\n", "\\PY{l+s+sd}{ 3:||fibonacci(3)}\n", "\\PY{l+s+sd}{ 4:|||fibonacci(2)}\n", "\\PY{l+s+sd}{ 5:||||fibonacci(1)}\n", "\\PY{l+s+sd}{ 6:||||fibonacci(0)}\n", "\\PY{l+s+sd}{ 7:|||fibonacci(1)}\n", "\\PY{l+s+sd}{ 8:||fibonacci(2)}\n", "\\PY{l+s+sd}{ 9:|||fibonacci(1)}\n", "\\PY{l+s+sd}{ 10:|||fibonacci(0)}\n", "\\PY{l+s+sd}{ 11:|fibonacci(3)}\n", "\\PY{l+s+sd}{ 12:||fibonacci(2)}\n", "\\PY{l+s+sd}{ 13:|||fibonacci(1)}\n", "\\PY{l+s+sd}{ 14:|||fibonacci(0)}\n", "\\PY{l+s+sd}{ 15:||fibonacci(1)}\n", "\\PY{l+s+sd}{ Done}\n", "\\PY{l+s+sd}{ 5}\n", "\\PY{l+s+sd}{ \\PYZdq{}\\PYZdq{}\\PYZdq{}}\n", "\n", " \\PY{n+nd}{@functools}\\PY{o}{.}\\PY{n}{wraps}\\PY{p}{(}\\PY{n}{f}\\PY{p}{)} \\PY{c+c1}{\\PYZsh{} give wrapper the identity of f and more}\n", " \\PY{k}{def} \\PY{n+nf}{wrapper}\\PY{p}{(}\\PY{o}{*}\\PY{n}{args}\\PY{p}{,} \\PY{o}{*}\\PY{o}{*}\\PY{n}{kwargs}\\PY{p}{)}\\PY{p}{:}\n", " \\PY{k}{nonlocal} \\PY{n}{count}\\PY{p}{,} \\PY{n}{depth}\n", " \\PY{n}{count} \\PY{o}{+}\\PY{o}{=} \\PY{l+m+mi}{1}\n", " \\PY{n}{depth} \\PY{o}{+}\\PY{o}{=} \\PY{l+m+mi}{1}\n", " \\PY{n}{call} \\PY{o}{=} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+si}{\\PYZob{}\\PYZcb{}}\\PY{l+s+si}{\\PYZob{}\\PYZcb{}}\\PY{l+s+s2}{\\PYZdq{}}\\PY{o}{.}\\PY{n}{format}\\PY{p}{(}\\PY{n}{f}\\PY{o}{.}\\PY{n+nv+vm}{\\PYZus{}\\PYZus{}name\\PYZus{}\\PYZus{}}\\PY{p}{,} \\PY{n}{\\PYZus{}argument\\PYZus{}string}\\PY{p}{(}\\PY{o}{*}\\PY{n}{args}\\PY{p}{,} \\PY{o}{*}\\PY{o}{*}\\PY{n}{kwargs}\\PY{p}{)}\\PY{p}{)}\n", " \\PY{n+nb}{print}\\PY{p}{(}\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+si}{\\PYZob{}:\\PYZgt{}3\\PYZcb{}}\\PY{l+s+s2}{:}\\PY{l+s+si}{\\PYZob{}\\PYZcb{}}\\PY{l+s+si}{\\PYZob{}\\PYZcb{}}\\PY{l+s+s2}{\\PYZdq{}}\\PY{o}{.}\\PY{n}{format}\\PY{p}{(}\\PY{n}{count}\\PY{p}{,} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{|}\\PY{l+s+s2}{\\PYZdq{}} \\PY{o}{*} \\PY{n}{depth}\\PY{p}{,} \\PY{n}{call}\\PY{p}{)}\\PY{p}{)}\n", "\n", " \\PY{n}{value} \\PY{o}{=} \\PY{n}{f}\\PY{p}{(}\\PY{o}{*}\\PY{n}{args}\\PY{p}{,} \\PY{o}{*}\\PY{o}{*}\\PY{n}{kwargs}\\PY{p}{)} \\PY{c+c1}{\\PYZsh{} calls f}\n", "\n", " \\PY{n}{depth} \\PY{o}{\\PYZhy{}}\\PY{o}{=} \\PY{l+m+mi}{1}\n", " \\PY{k}{if} \\PY{n}{depth} \\PY{o}{==} \\PY{o}{\\PYZhy{}}\\PY{l+m+mi}{1}\\PY{p}{:}\n", " \\PY{n+nb}{print}\\PY{p}{(}\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{Done}\\PY{l+s+s2}{\\PYZdq{}}\\PY{p}{)}\n", " \\PY{n}{count} \\PY{o}{=} \\PY{l+m+mi}{0}\n", " \\PY{k}{return} \\PY{n}{value}\n", "\n", " \\PY{n}{count}\\PY{p}{,} \\PY{n}{depth} \\PY{o}{=} \\PY{l+m+mi}{0}\\PY{p}{,} \\PY{o}{\\PYZhy{}}\\PY{l+m+mi}{1}\n", " \\PY{k}{return} \\PY{n}{wrapper} \\PY{c+c1}{\\PYZsh{} return the decorated function}\n", "\n", "\n", "\\PY{k}{def} \\PY{n+nf}{caching}\\PY{p}{(}\\PY{n}{f}\\PY{p}{)}\\PY{p}{:}\n", " \\PY{l+s+sd}{\\PYZdq{}\\PYZdq{}\\PYZdq{}Cache the return value of a function that takes a single argument.}\n", "\n", "\\PY{l+s+sd}{ Parameters:}\n", "\\PY{l+s+sd}{ \\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}}\n", "\\PY{l+s+sd}{ f: Callable}\n", "\\PY{l+s+sd}{ A function that takes a single argument.}\n", "\n", "\\PY{l+s+sd}{ Returns:}\n", "\\PY{l+s+sd}{ \\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}}\n", "\\PY{l+s+sd}{ Callable:}\n", "\\PY{l+s+sd}{ The function same as f but has its return valued automatically cached}\n", "\\PY{l+s+sd}{ when called. It has a method clear\\PYZus{}cache to clear its cache.}\n", "\n", "\\PY{l+s+sd}{ Examples:}\n", "\\PY{l+s+sd}{ \\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}\\PYZhy{}}\n", "\\PY{l+s+sd}{ \\PYZgt{}\\PYZgt{}\\PYZgt{} @print\\PYZus{}function\\PYZus{}call}\n", "\\PY{l+s+sd}{ ... @caching}\n", "\\PY{l+s+sd}{ ... def fibonacci(n):}\n", "\\PY{l+s+sd}{ ... return fibonacci(n \\PYZhy{} 1) + fibonacci(n \\PYZhy{} 2) if n \\PYZgt{} 1 else 1 if n == 1 else 0}\n", "\\PY{l+s+sd}{ ...}\n", "\\PY{l+s+sd}{ \\PYZgt{}\\PYZgt{}\\PYZgt{} fibonacci(5)}\n", "\\PY{l+s+sd}{ 1:fibonacci(5)}\n", "\\PY{l+s+sd}{ 2:|fibonacci(4)}\n", "\\PY{l+s+sd}{ 3:||fibonacci(3)}\n", "\\PY{l+s+sd}{ 4:|||fibonacci(2)}\n", "\\PY{l+s+sd}{ 5:||||fibonacci(1)}\n", "\\PY{l+s+sd}{ 6:||||fibonacci(0)}\n", "\\PY{l+s+sd}{ 7:|||fibonacci(1)}\n", "\\PY{l+s+sd}{ read from cache}\n", "\\PY{l+s+sd}{ 8:||fibonacci(2)}\n", "\\PY{l+s+sd}{ read from cache}\n", "\\PY{l+s+sd}{ 9:|fibonacci(3)}\n", "\\PY{l+s+sd}{ read from cache}\n", "\\PY{l+s+sd}{ Done}\n", "\\PY{l+s+sd}{ 5}\n", "\\PY{l+s+sd}{ \\PYZgt{}\\PYZgt{}\\PYZgt{} fibonacci(5)}\n", "\\PY{l+s+sd}{ 1:fibonacci(5)}\n", "\\PY{l+s+sd}{ read from cache}\n", "\\PY{l+s+sd}{ Done}\n", "\\PY{l+s+sd}{ 5}\n", "\\PY{l+s+sd}{ \\PYZgt{}\\PYZgt{}\\PYZgt{} fibonacci.clear\\PYZus{}cache()}\n", "\\PY{l+s+sd}{ \\PYZgt{}\\PYZgt{}\\PYZgt{} fibonacci(5)}\n", "\\PY{l+s+sd}{ 1:fibonacci(5)}\n", "\\PY{l+s+sd}{ 2:|fibonacci(4)}\n", "\\PY{l+s+sd}{ 3:||fibonacci(3)}\n", "\\PY{l+s+sd}{ 4:|||fibonacci(2)}\n", "\\PY{l+s+sd}{ 5:||||fibonacci(1)}\n", "\\PY{l+s+sd}{ 6:||||fibonacci(0)}\n", "\\PY{l+s+sd}{ 7:|||fibonacci(1)}\n", "\\PY{l+s+sd}{ read from cache}\n", "\\PY{l+s+sd}{ 8:||fibonacci(2)}\n", "\\PY{l+s+sd}{ read from cache}\n", "\\PY{l+s+sd}{ 9:|fibonacci(3)}\n", "\\PY{l+s+sd}{ read from cache}\n", "\\PY{l+s+sd}{ Done}\n", "\\PY{l+s+sd}{ 5}\n", "\\PY{l+s+sd}{ \\PYZdq{}\\PYZdq{}\\PYZdq{}}\n", "\n", " \\PY{n+nd}{@functools}\\PY{o}{.}\\PY{n}{wraps}\\PY{p}{(}\\PY{n}{f}\\PY{p}{)}\n", " \\PY{k}{def} \\PY{n+nf}{wrapper}\\PY{p}{(}\\PY{n}{n}\\PY{p}{)}\\PY{p}{:}\n", " \\PY{k}{if} \\PY{n}{n} \\PY{o+ow}{not} \\PY{o+ow}{in} \\PY{n}{cache}\\PY{p}{:}\n", " \\PY{n}{cache}\\PY{p}{[}\\PY{n}{n}\\PY{p}{]} \\PY{o}{=} \\PY{n}{f}\\PY{p}{(}\\PY{n}{n}\\PY{p}{)}\n", " \\PY{k}{else}\\PY{p}{:}\n", " \\PY{n+nb}{print}\\PY{p}{(}\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{read from cache}\\PY{l+s+s2}{\\PYZdq{}}\\PY{p}{)}\n", " \\PY{k}{return} \\PY{n}{cache}\\PY{p}{[}\\PY{n}{n}\\PY{p}{]}\n", "\n", " \\PY{n}{cache} \\PY{o}{=} \\PY{p}{\\PYZob{}}\\PY{p}{\\PYZcb{}}\n", " \\PY{n}{wrapper}\\PY{o}{.}\\PY{n}{clear\\PYZus{}cache} \\PY{o}{=} \\PY{k}{lambda}\\PY{p}{:} \\PY{n}{cache}\\PY{o}{.}\\PY{n}{clear}\\PY{p}{(}\\PY{p}{)} \\PY{c+c1}{\\PYZsh{} add method to clear cache}\n", " \\PY{k}{return} \\PY{n}{wrapper}\n", "\\end{Verbatim}\n" ], "text/plain": [ "'''\n", "Recursion Tools\n", "===============\n", "\n", "Contain tools to print the call stack and cache the return values\n", "of a recursive function.\n", "'''\n", "\n", "import functools\n", "\n", "\n", "def _argument_string(*args, **kwargs):\n", " \"\"\"Return the string representation of the list of arguments.\"\"\"\n", " return \"({})\".format(\n", " \", \".join(\n", " [\n", " *[\"{!r}\".format(v) for v in args], # arguments\n", " *[\n", " \"{}={!r}\".format(k, v) for k, v in kwargs.items()\n", " ], # keyword arguments\n", " ]\n", " )\n", " )\n", "\n", "\n", "def print_function_call(f):\n", " \"\"\"Decorate a recursive function to print the call stack.\n", "\n", " The decorator also keep tracks of the number and depth of a recursive call to print the call stack.\n", "\n", " Parameters\n", " ----------\n", " f: Callable\n", " A recursive function.\n", "\n", " Returns\n", " -------\n", " Callable:\n", " The decorated function that also prints the function call\n", " when called.\n", "\n", " Examples:\n", " ---------\n", " >>> @print_function_call\n", " ... def fibonacci(n):\n", " ... return fibonacci(n - 1) + fibonacci(n - 2) if n > 1 else 1 if n == 1 else 0\n", " ...\n", " >>> fibonacci(5)\n", " 1:fibonacci(5)\n", " 2:|fibonacci(4)\n", " 3:||fibonacci(3)\n", " 4:|||fibonacci(2)\n", " 5:||||fibonacci(1)\n", " 6:||||fibonacci(0)\n", " 7:|||fibonacci(1)\n", " 8:||fibonacci(2)\n", " 9:|||fibonacci(1)\n", " 10:|||fibonacci(0)\n", " 11:|fibonacci(3)\n", " 12:||fibonacci(2)\n", " 13:|||fibonacci(1)\n", " 14:|||fibonacci(0)\n", " 15:||fibonacci(1)\n", " Done\n", " 5\n", " \"\"\"\n", "\n", " @functools.wraps(f) # give wrapper the identity of f and more\n", " def wrapper(*args, **kwargs):\n", " nonlocal count, depth\n", " count += 1\n", " depth += 1\n", " call = \"{}{}\".format(f.__name__, _argument_string(*args, **kwargs))\n", " print(\"{:>3}:{}{}\".format(count, \"|\" * depth, call))\n", "\n", " value = f(*args, **kwargs) # calls f\n", "\n", " depth -= 1\n", " if depth == -1:\n", " print(\"Done\")\n", " count = 0\n", " return value\n", "\n", " count, depth = 0, -1\n", " return wrapper # return the decorated function\n", "\n", "\n", "def caching(f):\n", " \"\"\"Cache the return value of a function that takes a single argument.\n", "\n", " Parameters:\n", " -----------\n", " f: Callable\n", " A function that takes a single argument.\n", "\n", " Returns:\n", " --------\n", " Callable:\n", " The function same as f but has its return valued automatically cached\n", " when called. It has a method clear_cache to clear its cache.\n", "\n", " Examples:\n", " ---------\n", " >>> @print_function_call\n", " ... @caching\n", " ... def fibonacci(n):\n", " ... return fibonacci(n - 1) + fibonacci(n - 2) if n > 1 else 1 if n == 1 else 0\n", " ...\n", " >>> fibonacci(5)\n", " 1:fibonacci(5)\n", " 2:|fibonacci(4)\n", " 3:||fibonacci(3)\n", " 4:|||fibonacci(2)\n", " 5:||||fibonacci(1)\n", " 6:||||fibonacci(0)\n", " 7:|||fibonacci(1)\n", " read from cache\n", " 8:||fibonacci(2)\n", " read from cache\n", " 9:|fibonacci(3)\n", " read from cache\n", " Done\n", " 5\n", " >>> fibonacci(5)\n", " 1:fibonacci(5)\n", " read from cache\n", " Done\n", " 5\n", " >>> fibonacci.clear_cache()\n", " >>> fibonacci(5)\n", " 1:fibonacci(5)\n", " 2:|fibonacci(4)\n", " 3:||fibonacci(3)\n", " 4:|||fibonacci(2)\n", " 5:||||fibonacci(1)\n", " 6:||||fibonacci(0)\n", " 7:|||fibonacci(1)\n", " read from cache\n", " 8:||fibonacci(2)\n", " read from cache\n", " 9:|fibonacci(3)\n", " read from cache\n", " Done\n", " 5\n", " \"\"\"\n", "\n", " @functools.wraps(f)\n", " def wrapper(n):\n", " if n not in cache:\n", " cache[n] = f(n)\n", " else:\n", " print(\"read from cache\")\n", " return cache[n]\n", "\n", " cache = {}\n", " wrapper.clear_cache = lambda: cache.clear() # add method to clear cache\n", " return wrapper" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from IPython.display import Code\n", "\n", "Code(filename=\"recurtools.py\", language=\"python\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The module provides the decorators `print_function_call` and `caching` defined earlier." ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:10.246664Z", "start_time": "2020-10-31T02:44:10.238773Z" }, "slideshow": { "slide_type": "-" } }, "outputs": [], "source": [ "import recurtools as rc\n", "\n", "\n", "@rc.print_function_call\n", "@rc.caching\n", "def factorial(n):\n", " return factorial(n - 1) if n > 1 else 1" ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "ExecuteTime": { "end_time": "2020-10-31T02:44:10.254591Z", "start_time": "2020-10-31T02:44:10.249209Z" }, "scrolled": true, "slideshow": { "slide_type": "-" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " 1:factorial(5)\n", " 2:|factorial(4)\n", " 3:||factorial(3)\n", " 4:|||factorial(2)\n", " 5:||||factorial(1)\n", "Done\n", " 1:factorial(5)\n", "read from cache\n", "Done\n", " 1:factorial(5)\n", " 2:|factorial(4)\n", " 3:||factorial(3)\n", " 4:|||factorial(2)\n", " 5:||||factorial(1)\n", "Done\n" ] }, { "data": { "text/plain": [ "1" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "factorial(5)\n", "factorial(5)\n", "factorial.clear_cache()\n", "factorial(5)" ] } ], "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 }