Source

talks / python-ctxdec / context managers solutions.ipynb

Full commit
{
 "metadata": {
  "name": ""
 },
 "nbformat": 3,
 "nbformat_minor": 0,
 "worksheets": [
  {
   "cells": [
    {
     "cell_type": "markdown",
     "metadata": {
      "slideshow": {
       "slide_type": "-"
      }
     },
     "source": [
      "# Context Managers\n",
      "Context managers allow you to call setup code before a block of code is executed and teardown when the code is done. They are very useful in resource management but handy in many other situations.\n",
      "\n",
      "A [context manager object](http://docs.python.org/2/reference/datamodel.html#with-statement-context-managers) has a an `__enter__(self)` method that is called at start of the `with` block and an `__exit__(self, exc_type, exc_value, traceback)` that is called at the end of the `with` block. (The extra arguments to `__exit__` are in case of exception).\n",
      "\n",
      "    with open('/path/to/file.txt') as fo:\n",
      "        process(fo)\n",
      "        \n",
      "is very much like the following (\"double humped code\" as Raymond calls it :)\n",
      "\n",
      "    fo = open('/path/to/file.txt')\n",
      "    try:\n",
      "        process(fo)\n",
      "    finally:\n",
      "        fo.close()\n",
      "\n",
      "\n",
      "## API\n",
      "A context manager object should have the following methods.\n",
      "\n",
      "### __enter__(self)\n",
      "Called before the code block inside the `with` is executed. Can return an object which is bound in the `as` clause.\n",
      "\n",
      "### __exit__(self, exc_type, exc_value, traceback)\n",
      "Called after the code block insided the `with` is executed (even when an exception is raised). Last 3 parameters are in case of an error, on normal execution they will be `None`.\n",
      "\n",
      "If `__exit__` returns `True`, exceptions are \"swallowed\".\n",
      "\n",
      "## Nesting\n",
      "Context managers can be nested (separated with ,). The `__enter__` will be called in order, the `__exit__` in reverse order."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "class echo(object):\n",
      "    def __init__(self, name):\n",
      "        self.name = name\n",
      "\n",
      "    def __enter__(self):\n",
      "        print('enter {}'.format(self.name))\n",
      "\n",
      "    def __exit__(self, exc_type, exc_value, traceback):\n",
      "        print('exit {}'.format(self.name))\n",
      "\n",
      "\n",
      "with echo('a'), echo('b'), echo('c'):\n",
      "    pass"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "output_type": "stream",
       "stream": "stdout",
       "text": [
        "enter a\n",
        "enter b\n",
        "enter c\n",
        "exit c\n",
        "exit b\n",
        "exit a\n"
       ]
      }
     ],
     "prompt_number": 1
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "## Notes\n",
      "\n",
      "* Context managers are in use in the standard library ([file objects](http://docs.python.org/2/library/stdtypes.html#file-objects), [locks](http://docs.python.org/2/library/threading.html#lock-objects) ...), and outside.\n",
      "* [contextlib](http://docs.python.org/2/library/contextlib.html) in the standard library help with writing context managers (more on that later)."
     ]
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "# Exercise: Greeter\n",
      "Write a context manager that print 'Hai' at the start of the block and 'Bai' at end."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "class greeter(object):\n",
      "    def __enter__(self):\n",
      "        print('Hai')\n",
      "\n",
      "    def __exit__(self, exc_type, exc_value, traceback):\n",
      "        print('Bai')"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": 6
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "with greeter():\n",
      "    print('Wassup?')\n",
      "# Should print\n",
      "# Hai\n",
      "# Wassup?\n",
      "# Bai"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "output_type": "stream",
       "stream": "stdout",
       "text": [
        "Hai\n",
        "Wassup?\n",
        "Bai\n"
       ]
      }
     ],
     "prompt_number": 7
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "# Exercise: Timing\n",
      "Write a context manager that times the code inside the `with` statement, it should get a name argument to print."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "from time import time\n",
      "\n",
      "\n",
      "class timed_block(object):\n",
      "    def __init__(self, name):\n",
      "        self.name = name\n",
      "\n",
      "    def __enter__(self):\n",
      "        self.start = time()\n",
      "\n",
      "    def __exit__(self, exc_type, exc_value, traceback):\n",
      "        duration = time() - self.start\n",
      "        print('{} took {:0.2f}sec'.format(self.name, duration))"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": 8
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "from time import sleep\n",
      "with timed_block('sleep'):\n",
      "    sleep(0.2)\n",
      "# Should print 'sleep took 0.2sec'"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "output_type": "stream",
       "stream": "stdout",
       "text": [
        "sleep took 0.20sec\n"
       ]
      }
     ],
     "prompt_number": 9
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "# Exercise: Closer\n",
      "Write a context manager that closes the object it is passed by calling `obj.close()`."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "class closing(object):\n",
      "    def __init__(self, obj):\n",
      "        self.obj = obj\n",
      "\n",
      "    def __enter__(self):\n",
      "        pass\n",
      "\n",
      "    def __exit__(self, exc_type, exc_value, traceback):\n",
      "        self.obj.close()"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": 11
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "class Stream(object):\n",
      "    def close(self):\n",
      "        print('Closing stream')\n",
      "        \n",
      "stream = Stream()\n",
      "with closing(stream):\n",
      "    pass\n",
      "# Should print 'Closing stream'"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "output_type": "stream",
       "stream": "stdout",
       "text": [
        "Closing stream\n"
       ]
      }
     ],
     "prompt_number": 13
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "# Exercise: Databases\n",
      "Write a context manager that gets a database connection, provides a cursor to the `with` block and then either commits if everything went well, otherwise rollbacks.\n",
      "\n",
      "See [DB2 API](http://www.python.org/dev/peps/pep-0249/) for database API in Python.\n",
      "\n",
      "However, all you need to know is that a connection `cursor` method will create a new cursor. e.g.:\n",
      "    \n",
      "    import sqlite3\n",
      "    conn = sqlite3.connect(':memory:')\n",
      "    cur = conn.cursor()"
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "class dbctx(object):\n",
      "    def __init__(self, conn):\n",
      "        self.conn = conn\n",
      "\n",
      "    def __enter__(self):\n",
      "        return self.conn.cursor()\n",
      "\n",
      "    def __exit__(self, exc_type, exc_value, traceback):\n",
      "        if exc_type is None:\n",
      "            print('Committing')\n",
      "            self.conn.commit()\n",
      "        else:\n",
      "            print('Rolling back')\n",
      "            self.conn.rollback()"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": 14
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "import sqlite3\n",
      "conn = sqlite3.connect(':memory:')\n",
      "with dbctx(conn) as cur:\n",
      "    cur.execute('SELECT 1')\n",
      "# commit\n",
      "\n",
      "with dbctx(conn) as cur:\n",
      "    cur.execute('SELECT * FROM whatever')\n",
      "# rollback"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "ename": "OperationalError",
       "evalue": "no such table: whatever",
       "output_type": "pyerr",
       "traceback": [
        "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mOperationalError\u001b[0m                          Traceback (most recent call last)",
        "\u001b[0;32m<ipython-input-16-ee366a6bdc16>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      6\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      7\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mdbctx\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mcur\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 8\u001b[0;31m     \u001b[0mcur\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexecute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'SELECT * FROM whatever'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      9\u001b[0m \u001b[0;31m# rollback\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
        "\u001b[0;31mOperationalError\u001b[0m: no such table: whatever"
       ]
      },
      {
       "output_type": "stream",
       "stream": "stdout",
       "text": [
        "Committing\n",
        "Rolling back\n"
       ]
      }
     ],
     "prompt_number": 16
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "# contextlib\n",
      "[contextlib](http://docs.python.org/2/library/contextlib.html) provides some functions that help with writing context managers. Notably the [contextmanager](http://docs.python.org/2/library/contextlib.html#contextlib.contextmanager) decorator."
     ]
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "# Exercise: contextlib\n",
      "Rewrite exercise 3 using the `contextlib` decorator."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "from contextlib import contextmanager\n",
      "\n",
      "\n",
      "@contextmanager\n",
      "def dbctx(conn):\n",
      "    try:\n",
      "        yield conn.cursor()\n",
      "        conn.commit()\n",
      "    except:\n",
      "        conn.rollback()\n",
      "        raise  # Need to re-raise"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    }
   ],
   "metadata": {}
  }
 ]
}