{ "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": {} } ] }