Introduction to Context Managers in Python
This is the third of a series of posts loosely associated with integration testing, mostly focused on Python.
Context managers are a way of allocating and releasing some sort of resource exactly where you need it. The simplest example is file access:
This is essentially equivalent to:
Locks are another example. Given:
then
replaces the more verbose:
In each case a bit of boilerplate is eliminated, and the “context” of the file or the lock is acquired, used, and released cleanly.
Context managers are a common idiom in Lisp, where they are usually defined
using macros (examples are with-open
and with-out-str
in Clojure and with-open-file
and with-output-to-string
in Common
Lisp).
Python, not having macros, must include context managers as part of
the language. Since 2.5, it does so, providing an easy mechanism
for rolling your own. Though the default, ‘low level’ way to make a context manager
is to make a class which follows the context management
protocol,
by implementing __enter__
and __exit__
methods, the simplest way
is using the contextmanager
decorator from the contextlib
library,
and invoking yield
in your context manager function in between the
setup and teardown steps.
Here is a context manager which you could use to time our threads-vs-processes testing, discussed previously:
Composition
Context managers can be composed very nicely. While you can certainly do the following,
in Python 2.7 or above, the following also works:
with Python 2.6, use contextlib.nested
does almost the same thing:
the difference being that with the 2.7+ syntax, you can use the value
yielded from the first context manager as the argument to the second
(e.g., with a(x, y) as A, b(A) as C:...
).
If multiple contexts occur together repeatedly, you can also roll them together into a new context manager:
What does all this have to do with testing? I have found that for complex integration tests where there is a lot of setup and teardown, context managers provide a helpful pattern for making compact, simple code, by putting the “context” (state) needed for any given test close to where it is actually needed (and not everywhere else). Careful isolation of state is an important aspect of functional programming.
More on the use of context managers in actual integration tests in the next post.
blog comments powered by Disqus