{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"# Using functional programming in Python like a boss: Generators, Iterators and Decorators\n",
"\n",
""
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"# Features of functions\n",
"1. First-class functions are objects and thus:\n",
" * Can be assigned to variables\n",
" * Can be stored in data structures\n",
" * Can be used as parameters\n",
" * Can be used as a return value\n",
"2. Higher order functions:\n",
" * Accept a function as an argument and/or return a function as a value.\n",
" * Create composite functions from simpler functions.\n",
" * Modify the behavior of existing functions.\n",
"3. Pure functions:\n",
" * Do not depend on hidden state, or equivalently depend only on their input.\n",
" * Evaluation of the function does not cause side effects\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"# A pure function\n",
"def my_min(x, y):\n",
" if x < y:\n",
" return x\n",
" else:\n",
" return y\n",
"\n",
"\n",
"# An impure function\n",
"# 1) Depends on global variable, 2) Changes its input\n",
"exponent = 2\n",
"\n",
"def my_powers(L):\n",
" for i in range(len(L)):\n",
" L[i] = L[i]**exponent\n",
" return L"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true,
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"# What can act as a function in Python?\n",
"1. A function object, created with the `def` statement.\n",
"2. A `lambda` anonymous function, restricted to an expression in single line.\n",
"3. An instance of a class implementing the `__call__` function."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"True\n"
]
}
],
"source": [
"def min_def(x, y):\n",
" return x if x < y else y\n",
"\n",
"min_lambda = lambda x, y: x if x < y else y\n",
"\n",
"class MinClass:\n",
" def __call__(self, x, y):\n",
" return x if x < y else y\n",
"\n",
"min_class = MinClass()\n",
"\n",
"print(min_def(2,3) == min_lambda(2, 3) == min_class(2,3))"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"# Common gotchas 1: Mutable Default Arguments\n",
"\n",
"* Python's default arguments are evaluated once when the function is **defined** and **not each time the function is called** (like say, in Ruby).\n",
"* If you use a mutable default argument and mutate it, you will and have mutated that object for all future calls to the function as well.\n",
"* Sometimes you can specifically \"exploit\" (read: use as intended) this behavior to maintain state between calls of a function. This is often done when writing a caching function."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"my_list: [12]\n",
"my_other_list: [12, 42]\n",
"my_list2: [12]\n",
"my_other_list2: [42]\n"
]
}
],
"source": [
"def append_to(element, to=[]):\n",
" to.append(element)\n",
" return to\n",
"\n",
"my_list = append_to(12)\n",
"print(\"my_list:\", my_list)\n",
"my_other_list = append_to(42)\n",
"print(\"my_other_list:\", my_other_list)\n",
"\n",
"def append_to2(element, to=None):\n",
" if to is None:\n",
" to = []\n",
" to.append(element)\n",
" return to\n",
"\n",
"my_list2 = append_to2(12)\n",
"print(\"my_list2:\", my_list2)\n",
"my_other_list2 = append_to2(42)\n",
"print(\"my_other_list2:\", my_other_list2)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"# Common gotchas 2: Late Binding Closures\n",
"\n",
"1. A closure occurs when a function has access to a local variable from an enclosing scope that has finished its execution.\n",
"2. Python's closures are *late binding*.\n",
"3. Values of variables used in closures are looked up at the time the inner function is **called** and **not when it is defined**."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"8\n",
"8\n",
"8\n",
"8\n",
"8\n"
]
}
],
"source": [
"def create_multipliers():\n",
" multipliers = []\n",
"\n",
" for i in range(5):\n",
" def multiplier(x):\n",
" return i * x\n",
" multipliers.append(multiplier)\n",
"\n",
" return multipliers\n",
"\n",
"for multiplier in create_multipliers():\n",
" print(multiplier(2))"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"# Higher order functions and decorators\n",
"\n",
"* Python functions are objects.\n",
"* Can be defined in functions.\n",
"* Can be assigned to variables.\n",
"* Can be used as function parameters or returned from functions.\n",
"* Decorators are syntactic sugar for higher order functions."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"
"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"hello world\n",
"hello world\n"
]
}
],
"source": [
"# Higher order functions\n",
"\n",
"def makebold(fn):\n",
" def wrapped():\n",
" return \"\" + fn() + \"\"\n",
" return wrapped\n",
"\n",
"def hello():\n",
" return \"hello world\"\n",
"\n",
"print(hello())\n",
"hello = makebold(hello)\n",
"print(hello())"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Hello. args: ('world', 'pythess'), kwargs: {'where': 'soho'}\n"
]
}
],
"source": [
"# Decorated function with *args and **kewargs\n",
"\n",
"def makebold(fn):\n",
" def wrapped(*args, **kwargs):\n",
" return \"\" + fn(*args, **kwargs) + \"\"\n",
" return wrapped\n",
"\n",
"@makebold # hello = makebold(hello)\n",
"def hello(*args, **kwargs):\n",
" return \"Hello. args: {}, kwargs: {}\".format(args, kwargs)\n",
"\n",
"print(hello('world', 'pythess', where='soho'))"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Hello. args: ('world', 'pythess'), kwargs: {'where': 'soho'}\n"
]
}
],
"source": [
"# Decorators can be combined\n",
"\n",
"def makeitalic(fn):\n",
" def wrapped(*args, **kwargs):\n",
" return \"\" + fn(*args, **kwargs) + \"\"\n",
" return wrapped\n",
"\n",
"def makebold(fn):\n",
" def wrapped(*args, **kwargs):\n",
" return \"\" + fn(*args, **kwargs) + \"\"\n",
" return wrapped\n",
"\n",
"@makeitalic\n",
"@makebold # hello = makeitalic(makebold(hello))\n",
"def hello(*args, **kwargs):\n",
" return \"Hello. args: {}, kwargs: {}\".format(args, kwargs)\n",
"\n",
"print(hello('world', 'pythess', where='soho'))"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Hello. args: ('world', 'pythess'), kwargs: {'where': 'soho'}\n"
]
}
],
"source": [
"# Decorators can be instances of callable classes\n",
"\n",
"class BoldMaker:\n",
" def __init__(self, fn):\n",
" self.fn = fn\n",
" def __call__(self, *args, **kwargs):\n",
" return \"\" + self.fn(*args, **kwargs) + \"\"\n",
"\n",
"@BoldMaker # hello = BoldMaker(hello)\n",
"def hello(*args, **kwargs):\n",
" return \"Hello. args: {}, kwargs: {}\".format(args, kwargs)\n",
"\n",
"# hello.__call__(*args, **kwargs)\n",
"print(hello('world', 'pythess', where='soho'))"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"
hello world
\n", "hello world
\n" ] } ], "source": [ "# Decorators can take arguments\n", "\n", "def enclose_in_tags(opening_tag, closing_tag): # returns a decorator\n", " def make_with_tags(fn): # returns a decorated function\n", " def wrapped(): # the function to be decorated (modified)\n", " return opening_tag + fn() + closing_tag\n", " return wrapped\n", " return make_with_tags\n", "\n", "# decorator function make_with_tags with the arguments in closure\n", "heading_decorator = enclose_in_tags('', '
')\n", "\n", "def hello():\n", " return \"hello world\"\n", "\n", "h1_hello = heading_decorator(hello)\n", "p_hello = paragraph_decorator(hello)\n", "h1_p_hello = heading_decorator(paragraph_decorator(hello))\n", "print(h1_hello())\n", "print(p_hello())\n", "print(h1_p_hello())" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "hello world
\n", "hello world
\n" ] } ], "source": [ "# Decorators with arguments combined\n", "\n", "def enclose_in_tags(opening_tag, closing_tag):\n", " def make_with_tags(fn):\n", " def wrapped():\n", " return opening_tag + fn() + closing_tag\n", " return wrapped\n", " return make_with_tags\n", "\n", "# hello = enclose_in_tags('', '
')(hello)\n", "@enclose_in_tags('', '
')\n", "def hello():\n", " return \"hello world\"\n", "\n", "print(hello())\n", "\n", "# hello = enclose_in_tags('', '
')(hello))\n", "@enclose_in_tags('', '
')\n", "def hello():\n", " return \"hello world\"\n", "\n", "print(hello())" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "hello world
\n" ] } ], "source": [ "# Decorators with arguments as instances of callable classes\n", "\n", "class TagEncloser:\n", " def __init__(self, opening_tag, closing_tag):\n", " self.opening_tag = opening_tag\n", " self.closing_tag = closing_tag\n", " def __call__(self, fn):\n", " def wrapped():\n", " return self.opening_tag + fn() + self.closing_tag\n", " return wrapped\n", "\n", "tag_h1 = TagEncloser('', '
')\n", "\n", "@tag_h1\n", "@tag_p\n", "def hello(): # hello = tag_h1(tag_p(hello))\n", " return \"hello world\"\n", "\n", "print(hello())" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Iterables and Iterators\n", "\n", "1. **Iteration** is a general term for taking each item of something, one after another. Any time you use a loop, explicit or implicit, to go over a group of items, that is iteration.\n", "2. An **iterable** is an anything that can be looped over. It either:\n", " - Has an `__iter__` method which returns an **iterator** for that object when you call `iter()` on it, or implicitly in a for loop.\n", " - Defines a `__getitem__` method that can take sequential indexes starting from zero (and raises an `IndexError` when the indexes are no longer valid).\n", "3. An **iterator** is:\n", " * A stateful helper object which defines a `__next__` method and will produce the next value when you call ``next()`` on it. If there are no further items, it raises the `StopIteration` exception.\n", " * An object that is *self-iterable* (meaning that it has an `__iter__` method that returns `self`).\n", " \n", "Therefore: An *iterable* is an object from which we can get an *iterator*. An *iterator* is **always** an *iterable*. An *iterable* **is not always** an *iterator* but will always **return** an *iterator*." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "