Toying with Context Managers February 27, 2012
As I promised in the code-generation using context managers post,
I wanted to review some more, rather surprising, examples where context managers prove handy.
So we all know we can use context managers for resource life-time management: before entering the
with
-suite we allocate (open) the resource, and when we leave the suite, we free (close) it –
but there’s much more to context managers than meets the eye.
Stacks, for Fun and Profit
We’ve seen this one in the code-generation post, but we can generalize this notion a bit. Every time
we enter a with
block, we’d append an element to a list, which we’d pop on exit. Consider this
snippet:
Nothing fancy, but that’s exactly how the code generation framework works: whenever you enter a new block, it’s pushed onto the “stack”, and whenever we leave the block, the top-of-stack element is popped. This is how we automatically take care of indentation, curly-braces, etc.
But of course this pattern is much more useful. Consider something like this:
We can also leverage this concept to run commands as different users. Here’s a sketch:
In essence, every time you want to make local/undoable changes to your state, this pattern proves helpful.
Contextbacks
Contextbacks, a pun on callbacks, are contexts you pass as arguments to other functions. Many times it’s useful to pass a before-function and an after-function, and contextbacks are a nice way to encapsulate this. So instead of this:
You get this:
Not a ground-breaking change, but I prefer it as it’s more concise.
Lightweight Asynchronism
This is probably my favorite use case for contexts: you can use them to pipeline or interleave long-lasting tasks. You can think of contexts are degenerate forms of coroutines, in which you have defined beginning and end, but the middle part is interchangeable, so you can stick anything into it.
Imagine you need to format a harddisk (using mkfs
) or perform some long network operation (like
copying a huge file over scp
). The pattern is as follows: initiate the operation, wait for it
to finish, and either return or raise an exception. This fits perfectly well with the way contexts
work – with one change – yield
instead of waiting.
This saves you time: instead of waiting for the first operation to finish before starting with the
second, you can run them in parallel. The total time would be that of the longest task (not taking
into account the IO bottleneck). You can throw a yield
into any piece of code instead of just
blocking, and use it as a pipelined contextmanager. You can copy three files in parallel, without
resorting to threads or a reactor in the background.
Of course you could improve that by returning an object that reports the progress of the operation, e.g.
And voila! You have a thread-less, light-weight asynchronous framework at hand… a bit like using a reactor, but without rewriting your code.
And last, if you can’t tweak the blocking parts of the code (e.g., third party libraries), you can use the “defer to thread” or “defer to process” approach, a la twisted:
So that’s all I had in mind. If you have other unorthodox use cases for contexts, I’d love to hear about them!