Ever since Michael Foord talked about `ContextDecorator` in python-ideas I've been kicking around an idea for my own take on it. It's a `ContextManager` class which provides the same thing that Foord's `ContextDecorator` does, but also provides a few more goodies, chief of which being the `manage_context` method.
I've been working on this for a few days and I think it's ready for review. It's well-tested and extensively documented. I started using it wherever I have context managers in GarlicSim.
I'll be happy to get your opinions on my approach and any critiques you may have. If there are no problems with this approach, I'll probably release it with GarlicSim 0.6.1 and blog about it.
Here is my `context_manager` modulehttps://github.com/cool-RR/GarlicSim/blob/first_context_manager_review/garlicsim/garlicsim/general_misc/context_manager.py. Here are its testshttps://github.com/cool-RR/GarlicSim/tree/first_context_manager_review/garlicsim/test_garlicsim/test_general_misc/test_context_manager .
Following is the module's docstring which explains the module in more detail.
Defines the `ContextManager` and `ContextManagerType` classes.
These classes allow for greater freedom both when (a) defining context managers and when (b) using them.
Inherit all your context managers from `ContextManager` (or decorate your generator functions with `ContextManagerType`) to enjoy all the benefits described below.
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(): try: yield finally: pass # clean-up
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() try: with some_lock: yield self finally: do_some_cleanup()
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 some_lock` in our `manage_context`, which is shorter and more idiomatic than calling `some_lock.__enter__` in an `__enter__` method and `some_lock.__exit__` in an `__exit__` method.
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`.
That's it. Inherit all your context managers from `ContextManager` (or decorate your generator functions with `ContextManagerType`) to enjoy all these benefits.