{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Python: Functions\n", "\n", "To a large extent, data analysis consists of a sequence of data transformations using functions. Given the centrality of functions in this course, the notebook goes into some depth into Python function construction and usage." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Built-in functions\n", "\n", "See [Python docs](https://docs.python.org/2/library/functions.html)" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true }, "outputs": [], "source": [ "xs = ['apple', 'pear', 'grape', 'orange', 'rambutan', 'durian', 'longan', 'mango']" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['apple', 'durian', 'grape', 'longan', 'mango', 'orange', 'pear', 'rambutan']" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sorted(xs)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['pear', 'apple', 'grape', 'mango', 'orange', 'durian', 'longan', 'rambutan']" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sorted(xs, key=len)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['rambutan', 'orange', 'durian', 'longan', 'apple', 'grape', 'mango', 'pear']" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sorted(xs, key=len, reverse=True)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'rambutan'" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "max(xs)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'rambutan'" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "max(xs, key=len)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'apple'" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "min(xs)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'pear'" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "min(xs, key=len)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "45" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sum(map(len, xs))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Custom functions\n", "\n", "It is very simple to write a function. The docstring (a triple quoted string with 1 or more lines of function documentation is not mandatory but highly recommended). " ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def power(x, n=2):\n", " \"\"\"Returns x to the nth power.\n", " \n", " n has a default value of 2.\"\"\"\n", " \n", " return x**n" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Help on function power in module __main__:\n", "\n", "power(x, n=2)\n", " Returns x to the nth power.\n", " \n", " n has a default value of 2.\n", "\n" ] } ], "source": [ "help(power)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Use default value for exponent" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "9" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "power(3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Give explicit exponent value" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "81" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "power(3, 4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Give named arguments out of order" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1.7320508075688772" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "power(n=0.5, x=3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### With arbitrary arguments" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def f(a, b, *args, **kwargs):\n", " \"\"\"Example to illustrate use * and ** arguments.\"\"\"\n", " return a, b, args, kwargs" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": true }, "outputs": [], "source": [ "a, b, args, kwargs = f(1, 2, 3, 4, 5, x=10, y=11, z = 13)" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(1, 2)" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a, b" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(3, 4, 5)" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "args" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'x': 10, 'y': 11, 'z': 13}" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "kwargs" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Required keyword arguments" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def f1(a, b, *, c, d):\n", " \"\"\"c and d MUST be given as keyword arguments.\"\"\"\n", " return a, b, c, d" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(1, 2, 3, 4)" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f1(1, 2, c=3, d=4)" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "f1() takes 2 positional arguments but 4 were given\n" ] } ], "source": [ "try:\n", " f1(1, 2, 3, 4)\n", "except Exception as e:\n", " print(e)" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def f2(a, b, *args, c, d, **kwargs):\n", " \"\"\"Combining requyired and optional arguments.\"\"\"\n", " return a, b, c, d, args, kwargs" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "collapsed": true }, "outputs": [], "source": [ "a, b, c, d, args, kwargs = f2(1, 2, 3, 4, c=5, d=6, e=7, f=8)" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(1, 2)" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a, b," ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(5, 6)" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "c, d" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(3, 4)" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "args" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'e': 7, 'f': 8}" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "kwargs" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "f2() missing 2 required keyword-only arguments: 'c' and 'd'\n" ] } ], "source": [ "try:\n", " f2(1, 2, 3, 4, 5, 6, e=7, f=8)\n", "except Exception as e:\n", " print(e)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### All arguments are keyword only" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def f3(*, a, b, c):\n", " \"\"\"a, b and c must all be given as keyword arguments.\"\"\"\n", " return a, b, c" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(4, 2, 1)" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f3(c=1, b=2, a=4)" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "f3() takes 0 positional arguments but 3 were given\n" ] } ], "source": [ "try:\n", " f3(4, 2, 1)\n", "except Exception as e:\n", " print(e)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Expanding function arguments" ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def f4(a, b, c, d):\n", " return a, b, c, d" ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "collapsed": true }, "outputs": [], "source": [ "a = 1\n", "bc = [2, 3]\n", "d = 4" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(1, 2, 3, 4)" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f4(a, *bc, d)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Function annotations\n", "\n", "You can indicate the type of the arguments and return values using function annotations. Python itself does not do anything with these except put them in a dictionary under the `__annotations__ ` attribute, but 3rd party packages may use them if present. You will not be expected to use function annotations in this course." ] }, { "cell_type": "code", "execution_count": 36, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def power2(x : float, n : dict(type=float, help='exponent') =2) -> float:\n", " \"\"\"Returns x to the nth power.\n", " \n", " n has a default value of 2.\"\"\"\n", " \n", " return x**n" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'n': {'help': 'exponent', 'type': float}, 'return': float, 'x': float}" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "power2.__annotations__" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Lambda functions\n", "\n", "Also known as anonymous functions. These are often used to construct one-use-only short functions for higher order functions such as map, filter and reduce. " ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "collapsed": true }, "outputs": [], "source": [ "power2 = lambda x, n=2: x**n" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "9" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "power2(3)" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "81" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "power2(3, 4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Recursive functions\n", "\n", "A recursive function is one that calls itself. Python is not optimized for such functions and may crash if the recursion goes too deep. There is always a non-recursive version for any recursive algorithm that should be used instead, but this may not be obvious. Unlike functional languages with tail call optimization, recursive functions are rarely used in Python as they are usually slower and consume more memory than the equivalent non-recursive version.\n", "\n", "Recursive functions consist of \n", "\n", "- a base case which terminates the computation\n", "- a recursive call that MUST eventually end up in the base case" ] }, { "cell_type": "code", "execution_count": 41, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def factorial_1(n):\n", " \"\"\"Recursive factorial function.\"\"\"\n", " \n", " if n == 0:\n", " return 1\n", " else:\n", " return n * factorial_1(n-1)" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "30414093201713378043612608166064768844377641568960512000000000000" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "factorial_1(50)" ] }, { "cell_type": "code", "execution_count": 43, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from functools import reduce" ] }, { "cell_type": "code", "execution_count": 44, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def factorial_2(n):\n", " \"\"\"Non-recursive version.\"\"\"\n", " return reduce(lambda a, b: a*b, range(1, n+1))" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "30414093201713378043612608166064768844377641568960512000000000000" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "factorial_2(50)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### The non-recursive version is usually more time and memory efficient" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "14 µs ± 89.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)\n" ] } ], "source": [ "%timeit factorial_1(50)" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10.6 µs ± 71.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)\n" ] } ], "source": [ "%timeit factorial_2(50)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Higher order functions\n", "\n", "A higher order function is a function that takes another function as an argument or returns a function. Since functions are \"first class\" in Python, they can be treated in the same way as any other value. In particular, they can be used as function arguments and/or as return values." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Functions taking functions as arguments\n", "\n", "Classical higher order functions are map, filter and reduce, here presented with more Pythonic versions using built-ins and comprehensions. However, map, filter and reduce are still important because often parallel and distributed code is most efficiently coded using these **functional** operators." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Map" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0, 1, 8, 27, 64]" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list(map(lambda x: x**3, range(5)))" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0, 1, 8, 27, 64]" ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[x**3 for x in range(5)]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Filter" ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0, 2, 4]" ] }, "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list(filter(lambda x: x % 2 == 0,\n", " range(5)))" ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0, 2, 4]" ] }, "execution_count": 51, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[x for x in range(5) if x % 2 == 0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Reduce" ] }, { "cell_type": "code", "execution_count": 52, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from functools import reduce" ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "100" ] }, "execution_count": 53, "metadata": {}, "output_type": "execute_result" } ], "source": [ "reduce(lambda x, y: x+y, \n", " map(lambda x: x**3, range(5)))" ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "100" ] }, "execution_count": 54, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sum([x**3 for x in range(5)])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Using the operator module\n", "\n", "The operator modules provides function equivalents for all of Python's operators that are convenient for use in higher order functions." ] }, { "cell_type": "code", "execution_count": 55, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import operator as op" ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "100" ] }, "execution_count": 56, "metadata": {}, "output_type": "execute_result" } ], "source": [ "reduce(op.add, map(lambda x: x**3, range(5)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Custom function taking function arguments" ] }, { "cell_type": "code", "execution_count": 57, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def f(a, b, g, h):\n", " \"\"\"Function taking functions g and h as arguments.\"\"\"\n", " return g(a) + h(b)" ] }, { "cell_type": "code", "execution_count": 58, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "12" ] }, "execution_count": 58, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f(2, 2, lambda x: x**2, lambda x: x**3)" ] }, { "cell_type": "code", "execution_count": 59, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5" ] }, "execution_count": 59, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f('abc', -2, len, abs)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "### Functions returning functions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The partial function takes a function as argument and returns another function" ] }, { "cell_type": "code", "execution_count": 60, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from functools import partial" ] }, { "cell_type": "code", "execution_count": 61, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def f1(a, b, c):\n", " \"\"\"A function with 3 arguments.\"\"\"\n", " return a, b, c" ] }, { "cell_type": "code", "execution_count": 62, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(1, 2, 3)" ] }, "execution_count": 62, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f1(1, 2, 3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### f2 takes a single argument since b and c have been given values by partial" ] }, { "cell_type": "code", "execution_count": 63, "metadata": { "collapsed": true }, "outputs": [], "source": [ "f2 = partial(f1, b=12, c=13)" ] }, { "cell_type": "code", "execution_count": 64, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(11, 12, 13)" ] }, "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f2(11)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Custom function returning a function" ] }, { "cell_type": "code", "execution_count": 65, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def timed(f, *args, **kwargs):\n", " \"\"\"Decorates the function f with time takin in seconds.\"\"\"\n", " import time\n", " \n", " def func(*args, **kwargs):\n", " start = time.time()\n", " result = f(*args, **kwargs)\n", " elapsed = time.time() - start\n", " return elapsed, result\n", " return func" ] }, { "cell_type": "code", "execution_count": 66, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def my_sum(xs):\n", " s = 0\n", " for x in xs:\n", " s += x\n", " return s" ] }, { "cell_type": "code", "execution_count": 67, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "49999995000000" ] }, "execution_count": 67, "metadata": {}, "output_type": "execute_result" } ], "source": [ "my_sum(range(10000000))" ] }, { "cell_type": "code", "execution_count": 68, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(0.8492159843444824, 49999995000000)" ] }, "execution_count": 68, "metadata": {}, "output_type": "execute_result" } ], "source": [ "my_sum2 = timed(my_sum)\n", "my_sum2(range(10000000))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Decorators\n", "\n", "There is syntactic sugar to **decorate** functions when using functions such as the `timed` function above." ] }, { "cell_type": "code", "execution_count": 69, "metadata": { "collapsed": true }, "outputs": [], "source": [ "@timed\n", "def my_sum3(xs):\n", " s = 0\n", " for x in xs:\n", " s += x\n", " return s" ] }, { "cell_type": "code", "execution_count": 70, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(0.9857957363128662, 49999995000000)" ] }, "execution_count": 70, "metadata": {}, "output_type": "execute_result" } ], "source": [ "my_sum3(range(10000000))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Giving decorators their own arguments\n", "\n", "You will come across packages that provide decorators which can take arguments. This is one way that they can be implemented. See if you can follow how it works!" ] }, { "cell_type": "code", "execution_count": 71, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def timed2(fudge_factor=0.0):\n", " \"\"\"Decorator with decorartor arguments.\"\"\"\n", " def timed(f):\n", " import time\n", " def func(*args, **kwargs):\n", " start = time.time()\n", " result = f(*args, **kwargs)\n", " elapsed = fudge_factor + time.time() - start\n", " return elapsed, result\n", " return func\n", " return timed" ] }, { "cell_type": "code", "execution_count": 72, "metadata": { "collapsed": true }, "outputs": [], "source": [ "@timed2(fudge_factor=10)\n", "def my_sum4(xs):\n", " s = 0\n", " for x in xs:\n", " s += x\n", " return s" ] }, { "cell_type": "code", "execution_count": 73, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(11.009344100952148, 49999995000000)" ] }, "execution_count": 73, "metadata": {}, "output_type": "execute_result" } ], "source": [ "my_sum4(range(10000000))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "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.6.1" } }, "nbformat": 4, "nbformat_minor": 2 }