Monthly Archives: July 2009

python contextmanagers, subprocesses, and an ascii spinner

I have a command line script that takes too long to run. I profiled the script and the problem is in some fairly complex third-party code. So instead of fixing that stuff, I’m going to distract users with a pretty ascii spinner.

Run this script to see the spinner in action (you’ll have to kill the process afterward):


$ cat spin_forever.py 
# vim: set expandtab ts=4 sw=4 filetype=python:

import sys, time

def draw_ascii_spinner(delay=0.2):
    for char in '/-\|': # there should be a backslash in here.
        sys.stdout.write(char)
        sys.stdout.flush()
        time.sleep(delay)
        sys.stdout.write('\r') # this should be backslash r.

while "forever":
    draw_ascii_spinner()

Next I made a context manager (this is the name of the things you use with the new python “with” keyword) that uses the subprocess module to fork off another process to run the spinner:


$ cat distraction.py 
# vim: set expandtab ts=4 sw=4 filetype=python:

import contextlib, subprocess, time

@contextlib.contextmanager
def spinning_distraction():
    p = subprocess.Popen(['python', 'spin_forever.py'])
    yield
    p.terminate()

Finally I added my new context manager to the code that takes so long to run:

def main():

    with spinning_distraction():
        time.sleep(3) # pretend this is real work here.

if __name__ == '__main__':
    main()

If you’re following along at home, you can run python distraction.py and you’ll see the spinner go until the main application finishes sleeping.

The main thing I don’t like is how I’m using an external file named spin_forever.py. I don’t want to worry about the path to where spin_forever.py lives. I would prefer to use some function defined in a module that I can import.