## Automatic, temporary directory changing

| categories: programming | tags: | View Comments

If you are doing some analysis that requires you to change directories, e.g. to read a file, and then change back to another directory to read another file, you have probably run into problems if there is an error somewhere. You would like to make sure that the code changes back to the original directory after each error. We will look at a few ways to accomplish that here.

The try/except/finally method is the traditional way to handle exceptions, and make sure that some code "finally" runs. Let us look at two examples here. In the first example, we try to change into a directory that does not exist.

import os, sys

CWD = os.getcwd() # store initial position
print 'initially inside {0}'.format(os.getcwd())
TEMPDIR = 'data/run1' # this does not exist

try:
os.chdir(TEMPDIR)
print 'inside {0}'.format(os.getcwd())
except:
print 'Exception caught: ',sys.exc_info()[0]
finally:
print 'Running final code'
os.chdir(CWD)
print 'finally inside {0}'.format(os.getcwd())

initially inside c:\users\jkitchin\Dropbox\pycse
Exception caught:  <type 'exceptions.WindowsError'>
Running final code
finally inside c:\users\jkitchin\Dropbox\pycse


Now, let us look at an example where the directory does exist. We will change into the directory, run some code, and then raise an Exception.

import os, sys

CWD = os.getcwd() # store initial position
print 'initially inside {0}'.format(os.getcwd())
TEMPDIR = 'data'

try:
os.chdir(TEMPDIR)
print 'inside {0}'.format(os.getcwd())
print os.listdir('.')
raise Exception('boom')
except:
print 'Exception caught: ',sys.exc_info()[0]
finally:
print 'Running final code'
os.chdir(CWD)
print 'finally inside {0}'.format(os.getcwd())

initially inside c:\users\jkitchin\Dropbox\pycse
inside c:\users\jkitchin\Dropbox\pycse\data
['antoine_data.dat', 'antoine_database.mat', 'commonshellsettings.xml', 'cstr-zeroth-order.xlsx', 'debug-2.txt', 'debug-3.txt', 'debug-4.txt', 'debug.txt', 'example.xlsx', 'example2.xls', 'example3.xls', 'example4.xls', 'example4.xlsx', 'Flash_Example.apw', 'Flash_Example.bkp', 'Flash_Example.def', 'gc-data-21.txt', 'PT.txt', 'raman.txt', 'testdata.txt']
Exception caught:  <type 'exceptions.Exception'>
Running final code
finally inside c:\users\jkitchin\Dropbox\pycse


You can see that we changed into the directory, ran some code, and then caught an exception. Afterwards, we changed back to our original directory. This code works fine, but it is somewhat verbose, and tedious to write over and over. We can get a cleaner syntax with a context manager. The context manager uses the with keyword in python. In a context manager some code is executed on entering the "context", and code is run on exiting the context. We can use that to automatically change directory, and when done, change back to the original directory. We use the contextlib.contextmanager decorator on a function. With a function, the code up to a yield statement is run on entering the context, and the code after the yield statement is run on exiting. We wrap the yield statement in try/except/finally block to make sure our final code gets run.

import contextlib
import os, sys

@contextlib.contextmanager
def cd(path):
print 'initially inside {0}'.format(os.getcwd())
CWD = os.getcwd()

os.chdir(path)
print 'inside {0}'.format(os.getcwd())
try:
yield
except:
print 'Exception caught: ',sys.exc_info()[0]
finally:
print 'finally inside {0}'.format(os.getcwd())
os.chdir(CWD)

# Now we use the context manager
with cd('data'):
print os.listdir('.')
raise Exception('boom')

print
with cd('data/run2'):
print os.listdir('.')


One case that is not handled well with this code is if the directory you want to change into does not exist. In that case an exception is raised on entering the context when you try change into a directory that does not exist. An alternative class based context manager can be found here.