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.
A context manager object 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).
with open('/path/to/file.txt') as fo:
process(fo)
is very much like the following ("double humped code" as Raymond calls it :)
fo = open('/path/to/file.txt')
try:
process(fo)
finally:
fo.close()
A context manager object should have the following methods.
Called before the code block inside the with
is executed. Can return an object which is bound in the as
clause.
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
.
If __exit__
returns True
, exceptions are "swallowed".
Context managers can be nested (separated with ,). The __enter__
will be called in order, the __exit__
in reverse order.
In [1]:
class echo(object):
def __init__(self, name):
self.name = name
def __enter__(self):
print('enter {}'.format(self.name))
def __exit__(self, exc_type, exc_value, traceback):
print('exit {}'.format(self.name))
with echo('a'), echo('b'), echo('c'):
pass
In [6]:
class greeter(object):
def __enter__(self):
print('Hai')
def __exit__(self, exc_type, exc_value, traceback):
print('Bai')
In [7]:
with greeter():
print('Wassup?')
# Should print
# Hai
# Wassup?
# Bai
In [8]:
from time import time
class timed_block(object):
def __init__(self, name):
self.name = name
def __enter__(self):
self.start = time()
def __exit__(self, exc_type, exc_value, traceback):
duration = time() - self.start
print('{} took {:0.2f}sec'.format(self.name, duration))
In [9]:
from time import sleep
with timed_block('sleep'):
sleep(0.2)
# Should print 'sleep took 0.2sec'
In [11]:
class closing(object):
def __init__(self, obj):
self.obj = obj
def __enter__(self):
pass
def __exit__(self, exc_type, exc_value, traceback):
self.obj.close()
In [13]:
class Stream(object):
def close(self):
print('Closing stream')
stream = Stream()
with closing(stream):
pass
# Should print 'Closing stream'
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.
See DB2 API for database API in Python.
However, all you need to know is that a connection cursor
method will create a new cursor. e.g.:
import sqlite3
conn = sqlite3.connect(':memory:')
cur = conn.cursor()
In [14]:
class dbctx(object):
def __init__(self, conn):
self.conn = conn
def __enter__(self):
return self.conn.cursor()
def __exit__(self, exc_type, exc_value, traceback):
if exc_type is None:
print('Committing')
self.conn.commit()
else:
print('Rolling back')
self.conn.rollback()
In [16]:
import sqlite3
conn = sqlite3.connect(':memory:')
with dbctx(conn) as cur:
cur.execute('SELECT 1')
# commit
with dbctx(conn) as cur:
cur.execute('SELECT * FROM whatever')
# rollback
contextlib provides some functions that help with writing context managers. Notably the contextmanager decorator.
In [ ]:
from contextlib import contextmanager
@contextmanager
def dbctx(conn):
try:
yield conn.cursor()
conn.commit()
except:
conn.rollback()
raise # Need to re-raise