{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 3.4 Advanced Topics on Functions\n", "\n", "## Topics\n", "- handle variable length arguments\n", "- lambda expressions\n", "- higher-order functions\n", "- nested functions\n", "- functions as returned values\n", "- currying\n", "- function decorators" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3.4.1 Variable length arguments\n", "- when not sure how many arguments will be passed to a function (e.g., print())\n", "- *args (non-keyworded variable length arguments)\n", "- *kwargs (keyworded variable length arguments)\n", "- e.g., built-in print function uses variable length arguments\n", "\n", "### print(*object, sep=' ', end='\\n', file=sys.stdout, flush=False)" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# variable length arguments demo\n", "def someFunction(a, b, c, *varargs, **kwargs):\n", " print('a = ', a)\n", " print('b = ', b)\n", " print('c = ', c)\n", " print('*args = ', varargs)\n", " print('type of args = ', type(varargs))\n", " print('**kwargs = ', kwargs)\n", " print('type of kwargs = ', type(kwargs))" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a = 1\n", "b = Apple\n", "c = 4.5\n", "*args = (5, [2.5, 'b'])\n", "type of args = <class 'tuple'>\n", "**kwargs = {'fname': 'Jake', 'num': 1}\n", "type of kwargs = <class 'dict'>\n" ] } ], "source": [ "# call someFunction with some arguments\n", "someFunction(1, 'Apple', 4.5, 5, [2.5, 'b'], fname='Jake', num=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3.4.2 Lambda Functions/Expressions\n", "- anonymous functions (no name)\n", "- typically used in conjunction with higher order functions such as: map(), reduce(), filter()\n", "- Reference: http://www.secnetix.de/olli/Python/lambda_functions.hawk" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### lambda function properties and usage\n", "- single line simple functions\n", "- no explicit return keyword is used\n", "- always contains an expression that is implictly returned\n", "- can use a lambda definition anywere a function is expected without assigning to a variable\n", "- syntax: **lambda argument(s): expression**\n", "- see Ch08-2 Lists Advanced chapter for lambda applications on some higher order built-in functions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### difference between lambda and regular function" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "# regular function\n", "def func(x): return x**2" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "16\n" ] } ], "source": [ "print(func(4))" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "g = lambda x: x**2 # no name, no parenthesis, and no return keyword\n", "# a function that takes x and returns x**2" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "<function <lambda> at 0x7fc925e9dc80>\n" ] } ], "source": [ "print(g)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "16" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "g(4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3.4.3 Higher-order functions\n", "https://composingprograms.com/pages/16-higher-order-functions.html\n", "- functions that manipulate other functions are called higher order functions\n", "- functions take function(s) as argument(s)\n", " - typically lambda expressions are passed\n", "- functions can return a function" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [], "source": [ "# computer summations of n natural numbers\n", "# func is a function applied to all the natural numbers between 1 and n inclusive\n", "def sum_naturals(func, n):\n", " total, k = 0, 1\n", " while k <= n:\n", " total += func(k)\n", " k += 1\n", " return total\n", " " ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sum of first 100 natural numbers = 5050\n" ] } ], "source": [ "n = 100\n", "print(f'sum of first {n} natural numbers = {sum_naturals(lambda x: x, n)}')" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [], "source": [ "# of course you can pass regular function\n", "def even(n):\n", " return n if n%2 == 0 else 0" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sum of even numbers from 1 to 100 = 2550\n" ] } ], "source": [ "print(f'sum of even numbers from 1 to {n} = {sum_naturals(even, n)}')" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sum of odd numbers from 1 to 100 = 2500\n" ] } ], "source": [ "# sum of odd numbers from 1 to 100\n", "print(f'sum of odd numbers from 1 to {n} = {sum_naturals(lambda x: x if x%2==1 else 0, n)}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3.4.4 Nested definitions\n", "- functions can be defined inside a function with local scope\n", "- locally defined functions also have access to the names in which they are defined\n", " - this technique is called lexical scoping\n", "- helps keep the global frame clean and less cluter with functions that are only used inside some functions\n", "- let's redefine sum_natural function again with local functions\n", "\n", "### Visualize using [PythonTutor.com](http://pythontutor.com/visualize.html#code=def%20sum_naturals1%28n,%20number_type%3D%22all%22%29%3A%0A%20%20%20%20def%20even%28x%29%3A%0A%20%20%20%20%20%20%20%20return%20x%20if%20x%252%20%3D%3D%200%20else%200%0A%20%20%20%20%0A%20%20%20%20def%20odd%28x%29%3A%0A%20%20%20%20%20%20%20%20return%20x%20if%20x%252%20%3D%3D%201%20else%200%0A%20%20%20%20%0A%20%20%20%20def%20func%28x%29%3A%0A%20%20%20%20%20%20%20%20%23%20local%20function%20has%20access%20to%20global%20variables%20as%20well%20as%20parent%20frames%0A%20%20%20%20%20%20%20%20if%20number_type%20%3D%3D%20'even'%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20x%20if%20x%252%20%3D%3D%200%20else%200%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20x%20if%20x%252%20%3D%3D%201%20else%200%0A%20%20%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20total,%20k%20%3D%200,%201%0A%20%20%20%20while%20k%20%3C%3D%20n%3A%0A%20%20%20%20%20%20%20%20if%20number_type%20!%3D%20'all'%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20total%20%2B%3D%20func%28k%29%0A%20%20%20%20%20%20%20%20%23elif%20number_type%20%3D%3D%20'odd'%3A%0A%20%20%20%20%20%20%20%20%23%20%20%20%20total%20%2B%3D%20odd%28k%29%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20total%20%2B%3D%20k%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20return%20total%0A%20%20%20%20%0An%20%3D%2010%20%20%0Aprint%28f'sum%20of%20even%20numbers%20from%201%20to%20%7Bn%7D%20%3D%20%7Bsum_naturals1%28n,%20%22even%22%29%7D'%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false)" ] }, { "cell_type": "code", "execution_count": 102, "metadata": {}, "outputs": [], "source": [ "# compute summations of n natural numbers\n", "# by default sum_natural1 finds sum of all the natural numbers between 1 and n inclusive\n", "def sum_naturals1(n, number_type=\"all\"):\n", " def even(x):\n", " return x if x%2 == 0 else 0\n", " \n", " def odd(x):\n", " return x if x%2 == 1 else 0\n", " \n", " def func(x):\n", " # local function has access to global variables as well as parent frames\n", " if number_type == 'even':\n", " return x if x%2 == 0 else 0\n", " else:\n", " return x if x%2 == 1 else 0\n", " \n", " total, k = 0, 1\n", " while k <= n:\n", " if number_type != 'all':\n", " total += func(k)\n", " #elif number_type == 'odd':\n", " # total += odd(k)\n", " else:\n", " total += k\n", " k += 1\n", " return total" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sum of first 100 natural numbers = 5050\n" ] } ], "source": [ "n = 100\n", "print(f'sum of first {n} natural numbers = {sum_naturals1(n)}')" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sum of even numbers from 1 to 100 = 2550\n" ] } ], "source": [ "print(f'sum of even numbers from 1 to {n} = {sum_naturals1(n, \"even\")}')" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sum of odd numbers from 1 to 100 = 2500\n" ] } ], "source": [ "# sum of odd numbers from 1 to 100\n", "print(f'sum of odd numbers from 1 to {n} = {sum_naturals1(n, \"odd\")}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3.4.5 Functions as returned values\n", "- functions can return functions\n", "- locally defined functions maintain their parent environment when they are returned" ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [], "source": [ "def number_type(ntype='all'):\n", " def even(x):\n", " return x if x%2 == 0 else 0\n", " \n", " def odd(x):\n", " return x if x%2 == 1 else 0\n", " \n", " def _(x): # function to return x as it is; any()\n", " return x\n", " \n", " if ntype == 'all':\n", " return _\n", " elif ntype == 'even':\n", " return even\n", " else:\n", " return odd" ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sum of first 100 natural numbers = 5050\n" ] } ], "source": [ "n = 100\n", "print(f'sum of first {n} natural numbers = {sum_naturals(number_type(\"all\"), n)}')" ] }, { "cell_type": "code", "execution_count": 58, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sum of even numbers from 1 to 100 = 2550\n" ] } ], "source": [ "print(f'sum of even numbers from 1 to {n} = {sum_naturals(number_type(\"even\"), n)}')" ] }, { "cell_type": "code", "execution_count": 59, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sum of odd numbers from 1 to 100 = 2500\n" ] } ], "source": [ "# sum of odd numbers from 1 to 100\n", "print(f'sum of odd numbers from 1 to {n} = {sum_naturals(number_type(\"odd\"), n)}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3.4.6 Currying\n", "- functions that take multiple arguments can be converted into a chain of functions that each take a single argument using higher-order function\n", "- e.g., given a function **f(x, y)**, we can define a function **g(x)(y)** equivalent to **f(x, y)**\n", "- **g** is a higher-order function that takes in a single argument **x** and returns another function that takes in a single argument **y**\n", " - this transformation is called **currying**" ] }, { "cell_type": "code", "execution_count": 86, "metadata": {}, "outputs": [], "source": [ "def curried_pow(x):\n", " def g(y):\n", " return pow(x, y)\n", " return g" ] }, { "cell_type": "code", "execution_count": 87, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "8" ] }, "execution_count": 87, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# same as 2**3\n", "curried_pow(2)(3)" ] }, { "cell_type": "code", "execution_count": 88, "metadata": {}, "outputs": [], "source": [ "# let's create a list of integers and map each to a different value\n", "nums = list(range(1, 11))" ] }, { "cell_type": "code", "execution_count": 89, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]" ] }, "execution_count": 89, "metadata": {}, "output_type": "execute_result" } ], "source": [ "nums" ] }, { "cell_type": "code", "execution_count": 90, "metadata": {}, "outputs": [], "source": [ "def my_map(alist, func):\n", " for i in range(len(alist)):\n", " alist[i] = func(alist[i])" ] }, { "cell_type": "code", "execution_count": 91, "metadata": {}, "outputs": [], "source": [ "my_map(nums, curried_pow(2))" ] }, { "cell_type": "code", "execution_count": 92, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]" ] }, "execution_count": 92, "metadata": {}, "output_type": "execute_result" } ], "source": [ "nums" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3.4.7 Function Decorators\n", "- https://realpython.com/primer-on-python-decorators/\n", "- decorators are higher order functions\n", "- decorators take another function and extends the behavior of the latter function without explictly modifying it\n", "- if the func being decorated takes arguments, provide arguments to wrapper\n", "- if the func being decorated returns a value call it with return statement\n", "- many frameworks such as Flask, Django provide lots of decorators\n", " - e.g. @login_required; @app.route(\"/route_name\"), etc." ] }, { "cell_type": "code", "execution_count": 118, "metadata": {}, "outputs": [], "source": [ "# a simple decorator example\n", "# my_decorator decorates func\n", "def my_decorator(func):\n", " def wrapper():\n", " print(\"Before the function is called...\")\n", " # call the original function\n", " func()\n", " print(\"After the function is called.\")\n", " return wrapper\n", "\n", "def say_hello():\n", " print(\"Hello there!\")" ] }, { "cell_type": "code", "execution_count": 119, "metadata": {}, "outputs": [], "source": [ "# say_hello is decorated now, without modifying the original function\n", "# just the behavior is modified by added extra print() before and after say_hello\n", "say_hello = my_decorator(say_hello)" ] }, { "cell_type": "code", "execution_count": 120, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Before the function is called...\n", "Hello there!\n", "After the function is called.\n" ] } ], "source": [ "say_hello()" ] }, { "cell_type": "code", "execution_count": 100, "metadata": {}, "outputs": [], "source": [ "# Python provides better syntax!\n", "# use @decorting_function\n", "@my_decorator\n", "def say_hi():\n", " print(\"Hi there!\")" ] }, { "cell_type": "code", "execution_count": 101, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Before the function is called...\n", "Hi there!\n", "After the function is called.\n" ] } ], "source": [ "say_hi()" ] }, { "cell_type": "code", "execution_count": 107, "metadata": {}, "outputs": [], "source": [ "# a simple count down function\n", "def countDown(from_number):\n", " if from_number <= 0:\n", " print('Blast off!')\n", " else:\n", " print(from_number)\n", " countDown(from_number-1)" ] }, { "cell_type": "code", "execution_count": 112, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10\n", "9\n", "8\n", "7\n", "6\n", "5\n", "4\n", "3\n", "2\n", "1\n", "Blast off!\n" ] } ], "source": [ "# Doesn't slow down the countdown!\n", "countDown(10)" ] }, { "cell_type": "code", "execution_count": 109, "metadata": {}, "outputs": [], "source": [ "# let's write a slow_down wrapper\n", "import time\n", "\n", "def slow_down(func):\n", " \"\"\"Sleep 1 second before calling the function\"\"\"\n", " def wrapper_slow_down(*args, **kwargs):\n", " time.sleep(1) # sleep for a second\n", " return func(*args, **kwargs) # call and return the result from the func\n", " return wrapper_slow_down" ] }, { "cell_type": "code", "execution_count": 113, "metadata": {}, "outputs": [], "source": [ "countDownSlow = slow_down(countDown)" ] }, { "cell_type": "code", "execution_count": 114, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10\n", "9\n", "8\n", "7\n", "6\n", "5\n", "4\n", "3\n", "2\n", "1\n", "Blast off!\n" ] } ], "source": [ "countDownSlow(10)" ] }, { "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.3" } }, "nbformat": 4, "nbformat_minor": 2 }