I was re-writing the exellent [watchedinstall][watchedinstall] tool and needed to simplify a particularly gnarly chunk of code that required three sub-proceses to be started and then killed after invoking another process. It occurred to me I could make these into context managers.
Previously the code was something like…
start(program1)
try:
start(program2)
except:
stop(program1)
raise
try:
start(program3)
except:
stop(program2)
stop(program1)
raise
try:
mainprogram()
finally:
stop(program3)
stop(program2)
stop(program1)
Of course that could have been written with nested try / except / else / finally blocks as well, which I did start with but found not much shorter while almost incomprehensible.
[With context managers][ctxt] the whole thing was written as…
# from __future__ import with_statement, Python 2.5
with start(program1):
with start(program2):
with start(program3):
mainprogram()
So much more comprehensible! Here’s the implementation of the context manager (using the `contextlib.contextmanager` decorator for a triple word score):
import contextlib
import os
import signal
import subprocess
@contextlib.contextmanager
def start(program_args):
prog = subprocess.Popen(program_args)
if prog.poll(): # Anything other than None or 0 is BAD
raise subprocess.CalledProcessError(prog.returncode, program_args[0])
try:
yield
finally:
if prog.poll() is None:
os.kill(prog.pid, signal.SIGTERM)
For bonus points I might have used [`contexlib.nested()`][ctxtlib] to put the three `start()` calls on one line but then what would I do for the rest of the day?
[watchedinstall]: http://bitbucket.org/ptone/watchedinstall/
[ctxt]: http://docs.python.org/library/stdtypes.html#typecontextmanager
[ctxtlib]: http://docs.python.org/library/contextlib.html