with fail():

Sun, 20 Jan 2013 10:19:59 +0000

So, yesterday's article on with turned out the be a bit wrong. As lesson in reading the explanatory text and not just the example code. (Or possibly a lesson in providing better example code!).

So, I gave an example along the lines of:

@contextmanager
def chdir(path):
    # This code is wrong. Don't use it!
    cwd = os.getcwd()
    os.chdir(path)
    yield
    os.chdir(cwd)

Now, this appears to mostly work. The problem occurs when you have some code like:

with chdir('/'):
    raise Exception()

In this case the final cleanup code (os.chdir(cwd)) will not be executed, which is almost certainly not what you want. The correct was to write this is:

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

Writing it this way the final os.chdir(cwd) will be executed, and the exception will still be propagated. So I think this kind of sucks, because I'd really have preferred it to have this behaviour by default. To this end I've created a simplecontextmanager function decorator. Basically you can use it like so:

@simplecontextmanager
def chdir(path):
    cwd = os.getcwd()
    os.chdir(path)
    yield
    os.chdir(cwd)

Looks pretty much like the first example, however it will have the semantics of the second example. Now clearly the simplecontextmanager doesn't provide the whole range of flexibility that contextmanager does, but for simple code it is more straightforward.

My pyutil git repo has been updated to have working version of the chdir, umask and update_env context managers, and also contains the source for the new simplecontextmanager.

Moral of the story, test your code and read the docs carefully!

blog comments powered by Disqus