context_management
¶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.
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_management
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.
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_management.ContextManager):
... def __enter__(self):
... pass # preparation
... def __exit__(self, type_=None, value=None, traceback=None):
... pass # cleanup
As a decorated generator, like so:
>>> @context_management.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_management.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:
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.