..
   Copyright 2009-2012 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. The license may be obtained
   at http://creativecommons.org/licenses/by-sa/3.0/

.. _topics-context-managers:

:mod:`context_managers`
=======================

Context managers are awesome
----------------------------

I love context managers, and I love the :keyword:`with` keyword. If you've
never dealt with context managers or :keyword:`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 :keyword:`with` and context managers in your code contributes a lot to making your code more beautiful and maintainable. Every time you replace a :keyword:`try`\-:keyword:`finally` clause with a :keyword:`with` clause, an angel gets a pair of wings.

Now, you don't *need* any official :class:`ContextManager` class in order to
use context managers or define them; you just need to define
:meth:`__enter__` and :meth:`__exit__` methods in your class, and then you
can use your class as a context manager. *But*, if you use the
:class:`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 :class:`ContextManager` add?
--------------------------------------

The :class:`ContextManager` class allows using context managers as decorators
(in addition to their normal use) and supports writing context managers in a
new form called :meth:`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 :class:`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
  :meth:`__enter__` and :meth:`__exit__` methods. This is allowed, and if you
  do this you should still inherit from :class:`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 :func:`contextlib.contextmanager` decorator. One thing that is
  allowed here that :mod:`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 :meth:`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 :meth:`__enter__` and
:meth:`__exit__`; especially when using another context manager inside
:meth:`manage_context`. In our example we did ``with other_context_manager``
in our :meth:`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 :meth:`manage_context` approach over
:meth:`__enter__` and :meth:`__exit__` is that it's better at handling
exceptions, since any exceptions would be raised inside
:meth:`manage_context` where we could :keyword:`except` them, which is much
more idiomatic than the way :meth:`__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 :keyword:`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 :class:`contextlib.ContextDecorator`, but here it is
  combined with all the other goodies given by :class:`ContextManager`.
  Another advantage that :class:`ContextManager` has over
  :class:`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 :class:`ContextManager` (or
decorate your generator functions with :class:`ContextManagerType`) to enjoy
all of these benefits.


.. _here's a practical guide which explains how to use them.: http://effbot.org/zone/python-with-statement.htm
.. _Michele Simionato's excellent decorator module: http://pypi.python.org/pypi/decorator
