I happened upon a post whereby the author has:

a number of functions that all do very different things, but they all create and close a "context" [code example] I'd rather avoid the code duplication, and the danger of forgetting that try/finally block and the close().

The author's implementation was rather clever: using decorators to magically add the context, invoke the function passing the context instance as a keyword and then destroying the context after the function invocation. However, as the poster suggests, it's kinda kludgy and not explicit — thereby violating one of the tenets of The Zen of Python.

But, what's interesting, in some ways he basically invented the with statement introduced in Python 2.5 without knowing it. I really like with because it solves the often needed sequence of open-call-close rather abstractly and in quite a Pythonic way. My with solution shell:

from __future__ import with_statement # needed in 2.5, not in 2.6+

import logging
import traceback
logging.basicConfig(level=logging.INFO)

class Context(object):
    def __init__(self, i):
        self.i = i
        logging.info("(%d) initializing", self.i)

    def __enter__(self):
        logging.info("(%d) entering", self.i)

    def __exit__(self, exc_type, exc_val, exc_tb):
        logging.error("(%d) exiting", self.i)
        if exc_type:
            s = traceback.format_exception(exc_type, exc_val, exc_tb)
            logging.error("".join(s))
            return True

def g():
    for i in range (4):
        with Context(i) as ctx:
            if i == 2:
                # for demonstration purposes let's raise
                raise ValueError(i)

g()

The two magic methods required to participant in context management are:

  • __enter__: invoked when entering the context
  • __exit__: invoked when leaving the context; if the exception arguments are non-None an exception was raised — return True to suppress, False to re-raise

Running the code yields:

$ python b.py 
INFO:root:(0) initializing
INFO:root:(0) entering
ERROR:root:(0) exiting
INFO:root:(1) initializing
INFO:root:(1) entering
ERROR:root:(1) exiting
INFO:root:(2) initializing
INFO:root:(2) entering
ERROR:root:(2) exiting
ERROR:root:Traceback (most recent call last):
  File "b.py", line 26, in g
    raise ValueError(i)
ValueError: 2

INFO:root:(3) initializing
INFO:root:(3) entering
ERROR:root:(3) exiting
Categories: development 
comments powered by Disqus