A blog about the development and usage of GarlicSim, the open-source Pythonic framework for computer simulations.
Written by Ram Rachum, developer of GarlicSim.

GarlicSim Website

Twitter

GitHub Repository

Ram's Personal Website

2nd February 2011

Text

`ContextManager` and the `manage_context` method

GarlicSim module of the week

This is the fourth post in the GarlicSim module of the week series. This is a series of blog posts in which I explore different modules from the garlicsim.general_misc package. These are modules which are not specific to computer simulations; they can be relevant to any kind of Python program, and you are welcome to import them from garlicsim.general_misc and use them in your projects.

Part one: caching.cache: A caching decorator that understands arguments

Part two: caching.CachedType: A metaclass for sharing instances

Part three: address_tools: More powerful replacements for eval and repr 

Part four: ContextManager and the manage_context method

Part five: cute_profile: Profile your Python code on the fly

Context managers are awesome

I love context managers, and I love the with keyword. If you’ve never dealt with context managers or with, here’s a practical guide which explains how to use them. You may also read the more official PEP 343 which introduced these features to the language.

Using with and context managers in your code contributes a lot to making your code more beautiful and maintainable. Every time you replace a try-finally clause with a with clause, an angel gets a pair of wings.

Now, you don’t need any official ContextManager class in order to use context managers or define them; You just need to define __enter__ and __exit__ methods in your class, and then you can use your class as a context manager. But, if you use the ContextManager class as a base class to your context manager class, you could enjoy a few more features that might make your code a bit more concise and elegant.

What does ContextManager add?

The ContextManager class allows using context managers as decorators (in addition to their normal use) and supports writing context managers in a new form called manage_context. (As well as the original forms).

First let’s import:

from garlicsim.general_misc.context_manager import (ContextManager,
                                                    ContextManagerType,
                                                    SelfHook)

Now let’s go over the features one by one.

The ContextManager class allows you to define context managers in new ways and to use context managers in new ways. I’ll explain both of these; let’s start with defining context managers.

Defining context managers

There are 3 different ways in which context managers can be defined, and each has their own advantages and disadvantages over the others.

  1. The classic way to define a context manager is to define a class with __enter__ and __exit__ methods. This is allowed, and if you do this you should still inherit from ContextManager. Example:

    class MyContextManager(ContextManager):
        def __enter__(self):
            pass # preparation
        def __exit__(self, type_=None, value=None, traceback=None):
            pass # cleanup
    
  2. As a decorated generator, like so:

    @ContextManagerType
    def MyContextManager():
        # preparation
        try:
            yield
        finally:
            pass # cleanup
    

    The advantage of this approach is its brevity, and it may be a good fit for relatively simple context managers that don’t require defining an actual class.

    This usage is nothing new; It’s also available when using the standard library’s contextlib.contextmanager decorator. One thing that is allowed here that contextlib doesn’t allow is to yield the context manager itself by doing yield SelfHook.

  3. The third and novel way is by defining a class with a manage_context method which returns a decorator. Example:

    class MyContextManager(ContextManager):
        def manage_context(self):
            do_some_preparation()
            with other_context_manager:
                yield self
    

    This approach is sometimes cleaner than defining __enter__ and __exit__; especially when using another context manager inside manage_context. In our example we did with other_context_manager in our manage_context, which is shorter, more idiomatic and less double-underscore-y than the equivalent classic definition:

    class MyContextManager(object):
            def __enter__(self):
                do_some_preparation()
                other_context_manager.__enter__()
                return self
            def __exit__(self, *exc):
                return other_context_manager.__exit__(*exc)
    

    Another advantage of the manage_context approach over __enter__ and __exit__ is that it’s better at handling exceptions, since any exceptions would be raised inside manage_context where we could except them, which is much more idiomatic than the way __exit__ handles exceptions, which is by receiving their type and returning whether to swallow them or not.

These were the different ways of defining a context manager. Now let’s see the different ways of using a context manager:

Using context managers

There are 2 different ways in which context managers can be used:

  1. The plain old honest-to-Guido with keyword:
    with MyContextManager() as my_context_manager:
       do_stuff()
    
  2. As a decorator to a function:

    @MyContextManager()
    def do_stuff():
       pass # doing stuff
    

    When the do_stuff function will be called, the context manager will be used. This functionality is also available in the standard library of Python 3.2+ by using contextlib.ContextDecorator, but here it is combined with all the other goodies given by ContextManager. Another advantage that ContextManager has over ContextDecorator is that it uses Michele Simionato’s excellent decorator module to preserve the decorated function’s signature.

That’s it. Inherit all your context managers from ContextManager (or decorate your generator functions with ContextManagerType) to enjoy all of these benefits.

Source and tests for context_manager

Source for context_manager, well-documented. Here are the tests.

Yes, I used a double metaclass in the implementation, i.e. a metaclass which has a metaclass itself.

There is also a Python 3 version of context_manager, and here are its tests. It’s available with the Python 3 fork of GarlicSim.

Please give feedback!

I’ll be happy to get any opinions, critiques and code reviews on context_manager!

Comments
All content in this website is copyright © 1986-2011 Ram Rachum.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License, with attribution to "Ram Rachum at ram.rachum.com" including link to ram.rachum.com.
To view a copy of this license, visit: http://creativecommons.org/licenses/by-sa/3.0/