{
"metadata": {
"name": ""
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# CR\u00dcCIAL PYTHON Week 7: Decorators"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from IPython.core.display import Image\n",
"Image(url='http://labrosa.ee.columbia.edu/crucialpython/logo.png', width=600)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"html": [
""
],
"metadata": {},
"output_type": "pyout",
"prompt_number": 1,
"text": [
""
]
}
],
"prompt_number": 1
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This session of crucial python borrows heavily from \n",
"http://simeonfranklin.com/blog/2012/jul/1/python-decorators-in-12-steps/\n",
"which covers decorators from the ground up."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Background: Functions as first-class objects\n",
"In Python, functions are objects that you can pass around and manipulate just as you would a normal variable."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def say_hey():\n",
" \"\"\" A function which always returns the string 'hey' \"\"\"\n",
" return 'hey'\n",
"def print_function_output(function):\n",
" \"\"\" A function which calls an input function and prints its output \"\"\"\n",
" print function()\n",
"\n",
"print_function_output(say_hey)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"hey\n"
]
}
],
"prompt_number": 2
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# Functions can also be returned just like normal variables \n",
"def i_return_a_function():\n",
" \"\"\" A function which builds a new function and returns it \"\"\"\n",
" def i_get_returned():\n",
" \"\"\" This is a nested function, which gets built during i_return_a_function \"\"\"\n",
" print \"I was built in i_return_a_function\"\n",
" return i_get_returned\n",
"\n",
"result = i_return_a_function()\n",
"print result\n",
"result()"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"\n",
"I was built in i_return_a_function\n"
]
}
],
"prompt_number": 3
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Background: Function closures\n",
"When you define a function within another function, it will remember what the local namespace looked like *at definition time*."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def make_printer(print_me):\n",
" \"\"\" Construct a function which prints whatever was input to make_printer \"\"\"\n",
" def printer():\n",
" \"\"\" Print the value of print_me passed to make_printer when printer was created \"\"\"\n",
" print print_me\n",
" return printer\n",
"\n",
"hey_printer = make_printer('hey')\n",
"you_printer = make_printer('you')\n",
"hey_printer()\n",
"you_printer()"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"hey\n",
"you\n"
]
}
],
"prompt_number": 4
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Aside: `functools.partial` is a more useful example. From the documentation: \"The partial() is used for partial function application which \u201cfreezes\u201d some portion of a function\u2019s arguments and/or keywords resulting in a new object with a simplified signature.\""
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"print int.__doc__"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"int(x=0) -> int or long\n",
"int(x, base=10) -> int or long\n",
"\n",
"Convert a number or string to an integer, or return 0 if no arguments\n",
"are given. If x is floating point, the conversion truncates towards zero.\n",
"If x is outside the integer range, the function returns a long instead.\n",
"\n",
"If x is not a number or if base is given, then x must be a string or\n",
"Unicode object representing an integer literal in the given base. The\n",
"literal can be preceded by '+' or '-' and be surrounded by whitespace.\n",
"The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to\n",
"interpret the base from the string as an integer literal.\n",
">>> int('0b100', base=0)\n",
"4\n"
]
}
],
"prompt_number": 5
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"import functools\n",
"basetwo = functools.partial(int, base=2)\n",
"basetwo('0110')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 6,
"text": [
"6"
]
}
],
"prompt_number": 6
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Decorators!\n",
"Decorators are simply functions which take a function as input and return a function."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def double_function(function):\n",
" \"\"\" Return a version of function which doubles its output \"\"\"\n",
" def doubler():\n",
" return function()*2\n",
" return doubler\n",
"\n",
"def return_10():\n",
" return 10\n",
"return_double_10 = double_function(return_10)\n",
"print return_10()\n",
"print return_double_10()"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"10\n",
"20\n"
]
}
],
"prompt_number": 7
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The syntax `function = decorator(function)` ends up popping up a lot when you start using decorators. So, as a convenience, Python includes the @ syntax. Placing @decorator above a function definition replaces the function at definition time with its decorated version. You might remember seeing this syntax last week; it's used by `flask` extensively."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"@double_function\n",
"def return_20():\n",
" return 10\n",
"print return_20()"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"20\n"
]
}
],
"prompt_number": 8
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Practical Example\n",
"Say we have a bunch of functions, all of which take at least one input: `x`, which should be a float which is greater than 0. We can use a decorator to check for appropriate values of `x` and apply it to each function"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"import warnings\n",
"def validate_x(x_function):\n",
" \"\"\" Validates the input values of a function. \"\"\"\n",
" # The *args and **kwargs variables are any function arguments beyond the first\n",
" def x_function_validated(x, *args, **kwargs):\n",
" # Check that x is a float, and try casting it\n",
" if not type(x) is float:\n",
" warnings.warn('x is not a float')\n",
" try:\n",
" x = float(x)\n",
" except:\n",
" raise TypeError('Could not cast x to a float')\n",
" # Confirm that x is greater than 0\n",
" if not x > 0:\n",
" raise TypeError('x should be greater than 0')\n",
" return x_function(x, *args, **kwargs)\n",
" return x_function_validated\n",
"\n",
"@validate_x\n",
"def root(x, n):\n",
" \"\"\" Compute the n'th root of x \"\"\"\n",
" return x**(1/float(n))\n",
"\n",
"@validate_x\n",
"def invert_multiply_add(x, multiply=1., add=0.):\n",
" \"\"\" Computes multiply/x + add \"\"\"\n",
" return multiply/x + add"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 9
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"print root(16, 4)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"2.0\n"
]
},
{
"output_type": "stream",
"stream": "stderr",
"text": [
"-c:8: UserWarning: x is not a float\n"
]
}
],
"prompt_number": 10
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"print invert_multiply_add(0, 1)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"ename": "TypeError",
"evalue": "x should be greater than 0",
"output_type": "pyerr",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mprint\u001b[0m \u001b[0minvert_multiply_add\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m\u001b[0m in \u001b[0;36mx_function_validated\u001b[0;34m(x, *args, **kwargs)\u001b[0m\n\u001b[1;32m 13\u001b[0m \u001b[0;31m# Confirm that x is greater than 0\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 14\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mx\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 15\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'x should be greater than 0'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 16\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mx_function\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 17\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mx_function_validated\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mTypeError\u001b[0m: x should be greater than 0"
]
}
],
"prompt_number": 11
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"More substantial examples are available here:\n",
"https://wiki.python.org/moin/PythonDecoratorLibrary"
]
}
],
"metadata": {}
}
]
}