I was re-writing the exellent 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 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) try: yield finally: if prog.poll() is None: os.kill(prog.pid, signal.SIGTERM)
For bonus points I might have used
contexlib.nested() to put the three
start() calls on one line but then what would I do for the rest of the day?