Source

pythonwise / ctxdec / decorators solutions.ipynb

Full commit
{
 "metadata": {
  "name": ""
 },
 "nbformat": 3,
 "nbformat_minor": 0,
 "worksheets": [
  {
   "cells": [
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "# Decorators\n",
      "\n",
      "Decorators are a way to do [apect oriented programming](http://en.wikipedia.org/wiki/Aspect-oriented_programming) in Python.\n",
      "Decorators were added in Python 2.4 and many libraries do extensive use of them.\n",
      "\n",
      "In the simplest form\n",
      "   \n",
      "    @dec\n",
      "    def foo():\n",
      "        pass\n",
      "\n",
      "is equivalent to\n",
      "    \n",
      "    def foo():\n",
      "        pass\n",
      "    foo = dec(foo)\n",
      "    \n",
      "For the above you can see that a decorator is a function that takes a function as an argument and returns a function.\n",
      "\n",
      "## Notes\n",
      "Since their introduction in Python 2.4, decorators are everywhere. Including some in the standard library: [property](http://docs.python.org/2/library/functions.html#property), [staticmethod](http://docs.python.org/2/library/functions.html#staticmethod) and others. Many libraries also use decorators: [Celery](http://www.celeryproject.org/) `@task`, [Flask](http://flask.pocoo.org/) `@app.route` and many more.\n",
      "\n",
      "Decorators can be nested\n",
      "    \n",
      "    \n",
      "    @logged\n",
      "    @metrics.ncalls\n",
      "    @retry(times=3)\n",
      "    def whos_on_first():\n",
      "        return 'who'\n",
      "        \n",
      "        \n",
      "There are also class decorators (added in Python 2.6). You will probably won't need them (unless you wrote metaclasses before).\n",
      "\n",
      "    @plugin\n",
      "    class FunnyPlugin(object):\n",
      "        pass\n",
      "        "
     ]
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "# Exercise: Greeter\n",
      "Create a decorator that prints 'Hello FUNCTION_NAME' before the function start and 'By FUNCTION_NAME' after the function finishes.\n",
      "(You can can the function name from the `__name_\\__` attribute)."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "from functools import wraps\n",
      "def greeter(fn):\n",
      "    @wraps(fn)\n",
      "    def wrapper(*args, **kw):\n",
      "        print('Hello {}'.format(fn.__name__))\n",
      "        try:\n",
      "            return fn(*args, **kw)\n",
      "        finally:\n",
      "            print('Bye {}'.format(fn.__name__))\n",
      "\n",
      "    return wrapper"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": 2
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "@greeter\n",
      "def add(x, y):\n",
      "    '''Adds x to y'''\n",
      "    return x + y\n",
      "\n",
      "print(add(1, 2))"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "output_type": "stream",
       "stream": "stdout",
       "text": [
        "Hello add\n",
        "Bye add\n",
        "3\n"
       ]
      }
     ],
     "prompt_number": 3
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "# Exercise: Timer\n",
      "Write a `timed` decorators that logs timing information for a given function."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "from time import time\n",
      "\n",
      "def timed(fn):\n",
      "    @wraps(fn)\n",
      "    def wrapper(*args, **kw):\n",
      "        start = time()\n",
      "        try:\n",
      "            return fn(*args, **kw)\n",
      "        finally:\n",
      "            duration = time() - start\n",
      "            print('{} took ({:.2f}sec)'.format(fn.__name__, duration))\n",
      "\n",
      "    return wrapper"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": 18
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "from time import sleep\n",
      "@timed\n",
      "def mul(x, y):\n",
      "    '''Multiply x with y'''\n",
      "    sleep(0.2)\n",
      "    return x * y\n",
      "\n",
      "mul(8, 4)\n",
      "# return 32 and print timing info"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "output_type": "stream",
       "stream": "stdout",
       "text": [
        "mul took (0.20sec)\n"
       ]
      },
      {
       "metadata": {},
       "output_type": "pyout",
       "prompt_number": 19,
       "text": [
        "32"
       ]
      }
     ],
     "prompt_number": 19
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "# Bonus: Use timed_block\n",
      "Rewrite your decorator using `timed_block` context manager."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "from ctx import timed_block\n",
      "def timed(fn):\n",
      "    @wraps(fn)\n",
      "    def wrapper(*args, **kw):\n",
      "        with timed_block(fn.__name__):\n",
      "            return fn(*args, **kw)\n",
      "    return wrapper"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": 8
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "# Exercise: Caching\n",
      "Write a decorator that caches the results of the invoked function. Computing every value only once. (See [lru_cache](http://docs.python.org/dev/library/functools.html#functools.lru_cache) in Python > 3.2)."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "def cached(fn):\n",
      "    cache = {}  # In scope for wrapper\n",
      "    \n",
      "    def wrapper(*args, **kw):\n",
      "        if args not in cache:   # Ignore **kw for simplicity\n",
      "            cache[args] = fn(*args, **kw)\n",
      "        return cache[args]\n",
      "    \n",
      "    return wrapper"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": 1
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "@cached\n",
      "def fib(n):\n",
      "    '''Return the n'th fibonacci number'''\n",
      "    print('fib({})'.format(n))\n",
      "    if n < 2:\n",
      "        return 1\n",
      "    return fib(n-1) + fib(n-2)\n",
      "\n",
      "fib(5)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "output_type": "stream",
       "stream": "stdout",
       "text": [
        "fib(5)\n",
        "fib(4)\n",
        "fib(3)\n",
        "fib(2)\n",
        "fib(1)\n",
        "fib(0)\n"
       ]
      },
      {
       "metadata": {},
       "output_type": "pyout",
       "prompt_number": 2,
       "text": [
        "8"
       ]
      }
     ],
     "prompt_number": 2
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "# Exercise: Multiplier\n",
      "Write a decorator the multiplies the result of its wrapped function by `n`, where `n` is a parameter to the decorator."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "def mulby(n):\n",
      "    def wrapper(fn):\n",
      "        @wraps(fn)\n",
      "        def wrapped(*args, **kw):\n",
      "            return n * fn(*args, **kw)\n",
      "\n",
      "        return wrapped\n",
      "    return wrapper"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": 27
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "@mulby(7)\n",
      "def inc(x):\n",
      "    '''Add 1 to x'''\n",
      "    return x + 1\n",
      "\n",
      "inc(2)  # 21"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "metadata": {},
       "output_type": "pyout",
       "prompt_number": 28,
       "text": [
        "21"
       ]
      }
     ],
     "prompt_number": 28
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "# @property decorator\n",
      "\n",
      "[property](http://docs.python.org/2/library/functions.html#property) makes a function looks like an ordinary attribute. It let's you change your mind and have more controlled access to attribute (say locking) without any change to the client."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "class Parrot(object):\n",
      "    def __init__(self, voltage):\n",
      "        self._voltage = voltage\n",
      "        \n",
      "    @property\n",
      "    def voltage(self):\n",
      "        return self._voltage\n",
      "    \n",
      "p = Parrot(10)\n",
      "print(p.voltage)  # Note: Not a function call"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "output_type": "stream",
       "stream": "stdout",
       "text": [
        "10\n"
       ]
      }
     ],
     "prompt_number": 4
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [],
     "language": "python",
     "metadata": {},
     "outputs": []
    }
   ],
   "metadata": {}
  }
 ]
}