views:

262

answers:

4

In the question How do I "cd" in python, the accepted answer recommended wrapping the os.chdir call in a class to make the return to your original dir exception safe. Here was the recommended code:

class Chdir:         
  def __init__( self, newPath ):  
    self.savedPath = os.getcwd()
    os.chdir(newPath)

  def __del__( self ):
    os.chdir( self.savedPath )

Could someone elaborate on how this works to make an unsafe call exception safe?

+1  A: 

__del__ is called when the instance is about to be destroyed. So when you instantiate this class, the current working directory is saved to an instance attribute and then, well, os.chdir is called. When the instance is destroyed (for whatever reason) the current directory is changed to its old value.

This looks a bit incorrect to me. As far as I know, you must call parent's __del__ in your overriden __del__, so it should be more like this:

class Chdir(object):         
  def __init__(self, new_path):  
    self.saved_path = os.getcwd()
    os.chdir(new_path)

  def __del__(self):
    os.chdir(self.saved_path)
    super(Chdir, self).__del__()

That is, unless I am missing something, of course.

(By the way, can't you do the same using contextmanager?)

shylent
A: 

This code alone is neither thread-safe nor exception-safe. Actually I'm not really sure what you mean by exception-safe. Following code comes to mind:

try:
    # something thrilling
except:
    pass

And this is a terrible idea. Exceptions are not for guarding against. Well written code should catch exceptions and do something useful with them.

muhuk
I think by "exception safe" he meant the following: "even if the control is lost due to an exception being raised, we are guaranteed to return to the original directory".
shylent
Kind of funny that you say it isn't exception safe and then admit that you don't know what exception safe means.
Laurence Gonsalves
Regardless of what exception-safe means; can you say "this code alone is exception-safe"? By code I mean Chdir class in the question.
muhuk
Shylent - that's correct. That's what I was getting at by "exception safe".
zlovelady
+3  A: 

The direct answer to the question is: It doesn't, the posted code is horrible.

Something like the following could be reasonable to make it "exception safe" (but much better is to avoid chdir and use full paths instead):

  saved_path = os.getcwd()
  try:
    os.chdir(newPath)
    do_work()
  finally:
    os.chdir(saved_path)

And this precise behavior can also be written into a context manager.

kaizer.se
+2  A: 

Thread safety and exception safety are not really the same thing at all. Wrapping the os.chdir call in a class like this is an attempt to make it exception safe not thread safe.

Exception safety is something you'll frequently hear C++ developers talk about. It isn't talked about nearly as much in the Python community. From Boost's Exception-Safety in Generic Components document:

Informally, exception-safety in a component means that it exhibits reasonable behavior when an exception is thrown during its execution. For most people, the term “reasonable” includes all the usual expectations for error-handling: that resources should not be leaked, and that the program should remain in a well-defined state so that execution can continue.

So the idea in the code snippet you supplied is to ensure that in the case of the exception, the program will return to a well-defined state. In this case, the process will be returned in the directory it started from, whether os.chdir itself fails, or something causes an exception to be thrown and the "Chdir" instance to be deleted.

This pattern of using an object that exists merely for cleaning up is a form of "Resource Acquisition Is Initialization", or "RAII". This technique is very popular in C++, but is not so popular in Python for a few reasons:

  • Python has try...finally, which serves pretty much the same purpose and is the more common idiom in Python.
  • Destructors (__del__) in Python are unreliable/unpredicatble in some implementations, so using them in this way is somewhat discouraged. In cpython they happen to be very reliable and predictable as long as cycles aren't involved (ie: when deletion is handled by reference counting) but in other implementations (Jython and I believe also IronPython) deletion happens when the garbage collector gets around to it, which could be much later. (Interestingly, this doesn't stop most Python programmers from relying on __del__ to close their opened files.)
  • Python has garbage collection, so you don't need to be quite as careful about cleanup as you do in C++. (I'm not saying you don't have to be careful at all, just that in the common situations you can rely on the gc to do the right thing for you.)

A more "pythonic" way of writing the above code would be:

saved_path = os.getcwd()
os.chdir(new_path)
try:
    # code that does stuff in new_path goes here
finally:
    os.chdir(saved_path)
Laurence Gonsalves
Thanks Laurence. Very thorough and clear.
zlovelady