{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Argument Lists and Decorators\n",
    "\n",
    "## Variable-Length Arguments\n",
    "\n",
    "One way to create a function that can accept different numbers of arguments is to have default arguments for some of our parameters."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "def add(x, y=0, z=0):\n",
    "    return x + y + z"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "7"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# with 2 args\n",
    "add(3,4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "12"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# with 3 args\n",
    "add(3,4,5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This approach becomes cumbersome if there is a need to sometimes support a large number of arguments.  Furthermore, once we've defined the function, there will be a maximum number of arguments we can ever pass.\n",
    "\n",
    "Python supports another feature without these limitations, called *variable-length arguments*.  It uses the `*` operator for this feature (as well as for multiplication).\n",
    "\n",
    "It can be used two ways:\n",
    "1. convert a series of arguments passed in to a tuple (when defining a function)\n",
    "2. convert a sequence (tuple, list, etc) to a series of args (when calling a function)\n",
    "\n",
    "Let's see (1) first:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "def add(*nums):\n",
    "    print(nums)\n",
    "    print(type(nums))\n",
    "    total = 0\n",
    "    for x in nums:\n",
    "        total += x\n",
    "    return total"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "()\n",
      "<class 'tuple'>\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "0"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "add()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(1, 2)\n",
      "<class 'tuple'>\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "3"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "add(1, 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(1, 2, 3, 4, 5)\n",
      "<class 'tuple'>\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "15"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "add(1, 2, 3, 4, 5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, we're calling the function like normal, but instead of having parameters inside the parentheses of `def add(????):`, we have `*nums` there.  The star means that nums will be a tuple, containing whatever arguments get passed in, regardless of how many there are.\n",
    "\n",
    "Let's see use case (2) now, which is the reverse of the former example (now we're going from a sequence to a bunch of arguments):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1,2,3\n"
     ]
    }
   ],
   "source": [
    "print(1, 2, 3, sep=\",\") # regular argument passing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1,2,3\n"
     ]
    }
   ],
   "source": [
    "nums = [1, 2, 3]\n",
    "print(*nums, sep=\",\") # convert the nums list to a series of arguments"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Of course, this is especially useful we want to pass a large number arguments."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99\n"
     ]
    }
   ],
   "source": [
    "nums = list(range(100))\n",
    "print(*nums, sep=\",\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Decorators\n",
    "\n",
    "Sometimes it's useful to modify many of our functions in the same way.  For example, maybe we have several functions for doing arithmatic, and we want them all to print the incoming arguments (a weird example, but maybe for debugging).\n",
    "\n",
    "We could copy/paste the print statements to each, but copy/pasting often has its disadvantages (for example, if later we want to write to a file instead of printing, there might be dozens of functions we need to go back and change).\n",
    "\n",
    "Decorators solve this problem.  A decorator is a functions (1) that can replace regular functions (2) with yet other functions that have additional features (3).  This is strange, and one of the more challenging things to conceptualize in CS 320.  What makes it weirder still:\n",
    "\n",
    "* the `def` statement for the functions in part (3) are typically indented inside function (1)\n",
    "* every time function (1) is called, it may define a new function (3); over time, many functions might get defined with the same name\n",
    "* the decorator function (1) takes as an argument a reference to a function (2), and returns a reference to a function (3)\n",
    "\n",
    "Let's try an example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "def print_decorator(fn_before):\n",
    "    def fn_after(*args):\n",
    "        print(\"DEBUG\", args)\n",
    "        return \"TODO\"\n",
    "    return fn_after\n",
    "\n",
    "@print_decorator\n",
    "def add(x, y):\n",
    "    print(\"I am adding!!!\")\n",
    "    return x+y\n",
    "\n",
    "@print_decorator\n",
    "def sub(x, y):\n",
    "    return x-y\n",
    "\n",
    "@print_decorator\n",
    "def mult(x, y):\n",
    "    return x*y\n",
    "\n",
    "@print_decorator\n",
    "def div(x, y):\n",
    "    return x-y"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "DEBUG (3, 4)\n"
     ]
    }
   ],
   "source": [
    "result1 = add(3, 4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "DEBUG (1, 2)\n"
     ]
    }
   ],
   "source": [
    "result2 = sub(1, 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "DEBUG (8, 10)\n"
     ]
    }
   ],
   "source": [
    "result3 = mult(8, 10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "TODO\n",
      "TODO\n",
      "TODO\n"
     ]
    }
   ],
   "source": [
    "print(result1)\n",
    "print(result2)\n",
    "print(result3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What's happening here?  When `@print_decorator` appears before, say, the `add` function, the following happens:\n",
    "1. `print_decorator` automatically gets called\n",
    "2. a reference to the original new `add` function is passed to the `fn_before` paramater.  Note that we aren't using `fn_before` yet in this example\n",
    "3. `print_decorator` defines a new function, `fn_after`, which it it returns (note that this function takes a variable number of arguments -- this is common, but not required)\n",
    "4. `add` is redefined to be the same as `fn_after`, instead of the code the programmer originally wrote after the `def` statement\n",
    "\n",
    "That redefinition is why calling `add(3, 4)` results in the debug print, and a return value of \"TODO\", but we never see the \"I am adding!!!\" message printed.\n",
    "\n",
    "We can also see this if we look at `add` (without calling it!); it will say \"print_decorator\":"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<function __main__.print_decorator.<locals>.fn_after(*args)>"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "add"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "With most realistic use cases for decorators, we want to add some functionality to an existing function, without fulling replacing it.  This is where `fn_before` comes in.  `fn_after` can perform whatever extra functionality is is supposed to, but it will usually then call `fn_before` so that the original logic runs too.\n",
    "\n",
    "Let's modify the earlier example to do that instead of returning \"TODO\" in `fn_after`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "def print_decorator(fn_before):\n",
    "    def fn_after(*args):\n",
    "        print(\"DEBUG\", args)\n",
    "        return fn_before(*args)\n",
    "    return fn_after\n",
    "\n",
    "@print_decorator\n",
    "def add(x, y):\n",
    "    print(\"I am adding!!!\")\n",
    "    return x+y\n",
    "\n",
    "@print_decorator\n",
    "def sub(x, y):\n",
    "    return x-y\n",
    "\n",
    "@print_decorator\n",
    "def mult(x, y):\n",
    "    return x*y\n",
    "\n",
    "@print_decorator\n",
    "def div(x, y):\n",
    "    return x-y"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "DEBUG (3, 4)\n",
      "I am adding!!!\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "7"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "add(3, 4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Great!  Now we get the original functionionality of add (printing \"I am adding!!!\" and returning the sum), while getting the bonus functionality of the debug print.\n",
    "\n",
    "In general, it will just feel like we're using an enhanced version of the `add` function, but we can always look at the functions (again, without calling them) to see the swap-out that happened:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<function __main__.print_decorator.<locals>.fn_after(*args)>"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "add"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<function __main__.print_decorator.<locals>.fn_after(*args)>"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sub"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "One last detail: those two functions above are different, even though they have to have the same name(`fn_after`).  So we'll still get different results from calling `add` vs. `sub`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "DEBUG (3, 4)\n",
      "I am adding!!!\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "7"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "add(3, 4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "DEBUG (3, 4)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "-1"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sub(3, 4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "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.7.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
