Python context managers: they are easy!

This comes from this thread in the Python Argentina mailing list (which I strongly recommend if you read spanish).

I was the other day trying to do shell-script-like-things on python (as part of a monster setup.py) and I was annoyed that in shell it's easy to do this:

cd foo
bar -baz
cd -

Or this:

pushd foo
bar -baz
popd

Or this:

(cd foo && bar -baz)

And on Python I had to do this, which is verbose and ugly:

cwd = os.getcwd()
try:
    os.chdir('foo')
    os.system('bar -baz')
finally:
    os.chdir(cwd)

This is what I wanted to have:

with os.chdir('foo'):
    os.system('bar -baz')

And of course, you can't do that. So, I asked, how do you implement that context manager? I got several answers.

  1. That's available in Fabric:

    with cd("foo"):
        run("bar")
    
  2. It's not hard to do:

    class DirContextM(object):
        def __init__(self, new_dir):
            self.new_dir = new_dir
            self.old_dir = None
    
        def __enter__(self):
            self.old_dir = os.getcwd()
            os.chdir(self.new_dir)
    
        def __exit__(self, *_):
            os.chdir(self.old_dir)
    
  3. It's even easier to do:

    from contextlib import contextmanager
    
    @contextmanager
    def cd(path):
        old_dir = os.getcwd()
        os.chdir(path)
        yield
        os.chdir(old_dir)
    
  4. That's cool, so let's add it to path.py

  5. Maybe check for exceptions

    @contextmanager
    def cd(path):
        old_dir = os.getcwd()
        os.chdir(path)
        try:
            yield
        finally:
            os.chdir(old_dir)
    

All in all, I learned how to do context managers, about contextlib, about fabric and about path.py. Which is not bad for 15 minutes :-)

Comments

Comments powered by Disqus