### Navigation

- index
- next |
- previous |
- python_toolbox 0.1.0 documentation »
- Topical guides to the Python Toolbox »

# `context_managers`

## 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 python_toolbox import context_managers

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.

- 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(context_managers.ContextManager):
      ...     def __enter__(self):
      ...         pass # preparation
      ...     def __exit__(self, type_=None, value=None, traceback=None):
      ...         pass # cleanup

- As a decorated generator, like so:

      >>> @context_managers.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 context_managers.SelfHook`.

- 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:

- The plain old honest-to-Guido `with` keyword:

      >>> with MyContextManager() as my_context_manager:
      ...     do_stuff()

- 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 `contextlib.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.

### Navigation

- index
- next |
- previous |
- python_toolbox 0.1.0 documentation »
- Topical guides to the Python Toolbox »