Python language: the with statement and context managers
What ?
The with statement is used to wrap the execution of a code block, in such a manner that- a runtime context is set up, before the code block executes.
- the runtime context is teared down, after the code block has executed (no matter if an exception is raised inside of the code block).
Why ?
The with statement is a generalization of the try-finally construct.From the python docs:
The with statement behaves in the same way, guaranteeing that the 'cleanup handler' is always executed, even if an exception is raised. Compared to try-finally, usage of the with statement comes with some benefits:If finally is present, it specifies a ‘cleanup’ handler. The try clause is executed, including any except and else clauses. If an exception occurs in any of the clauses and is not handled, the exception is temporarily saved. The finally clause is executed. If there is a saved exception, it is re-raised at the end of the finally clause. If the finally clause raises another exception or executes a return or break statement, the saved exception is discarded:def f(): try: 1/0 finally: return 42 >>> f() 42The exception information is not available to the program during execution of the finally clause.
- by implementing the context manager, you can improve code reusability.
- if an exception was raised by your code block, the context manager's __exit__() method - which handles tearing down the runtime context - has access to this exception. When returning control to the with statement, exit() can either suppress the exception (= by returning true) or let the with statement re-raise the exception (= default).
How ?
There are two forms of the with statement. In the first, the context object does not return/yield a context-specific object to work with.In the second, the context provides us an object to be used within the context.with context : do_something()
The context manager can be implemented in two different ways: as a class or as a generator function. See the links below for details and examples:with context as variable: do_something(variable)
- This effbot page explains how the statement works
- http://preshing.com/20110920/the-python-with-statement-by-example/
- http://www.itmaybeahack.com/book/python-2.6/html/p03/p03c07_contexts.html : very good explanation
Context Manager as a Class
This is the most transparant way to implement a context manager: create a class in which has an __enter__() and __exit__() method. Here's an example of a lock with memcached:
class Lock():
def __init__(self, name):
self.key = "lock.%s" % name
def __enter__(self):
value = cache.add(self.key, "1", 60000)
return value
def __exit__(self, exc_type, exc_val, exc_tb):
cache.delete(self.key)
return False
The Lock context manager can be used like this:
with Lock("myname") aslock_acquired:iflock_acquired: # test the yielded bool, to see if the lock is acquireddoSomeExpensiveStuff()
Context Manager as a Generator Function
In this case, the @contextmanager decorator is used to transform a simple generator function into a context manager. All function code before the yield statement is responsible for the setup (same as __enter__), all code after the yield statement is responsible for the tear down (same as __exit__).See http://www.itmaybeahack.com/book/python-2.6/html/p03/p03c07_contexts.html#defining-a-context-manager-function for more details.
Our locking class could be rewritten like this:
@contextmanager def lock(name): key ="lock.%s" %name # setup starts herelock = cache.add(key, "1", 60000) yield lock # yield the lock result (True or False) to the with stmt if lock: # teardown starts here cache.delete(key)
The Lock context manager can be used like this:
See also these examples of memcached locking using generators:with lock("myname") as lock_acquired: iflock_acquired: # test the yielded bool, to see if the lock is acquireddoSomeExpensiveStuff()
- http://coffeeonthekeyboard.com/simple-out-of-process-lock-with-python-and-memcached-2-985/ : a simple lock
- http://russellneufeld.wordpress.com/2012/05/24/using-memcached-as-a-distributed-lock-from-within-django/ : lock with timeout
File access
In Python 2.5, the file object has been equipped with __enter__ and __exit__ methods out of the box; the former simply returns the file object itself, and the latter closes the file.So to open a file, process its contents, and make sure to close it, this is all the code you need:
with open("x.txt") as f:
data = f.read()
# do something with data
Comments