Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does wrapping an unsafe python method (e.g os.chdir) in a class make it thread/exception safe?

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?

like image 717
zlovelady Avatar asked Nov 10 '09 17:11

zlovelady


People also ask

What does OS chdir do in Python?

chdir() method in Python used to change the current working directory to specified path. It takes only a single argument as new directory path.

What do the OS Getcwd () and OS chdir () functions do?

getcwd() : CWD stands for Current Working Directory. This function allows you to see what your current working directory is. chdir("path-to-dir") : Short for CHange DIRectory, this function allows you to set the current working directory to a path of your choice.

What is the meaning of OS chdir demo )?

Description. Python method chdir() changes the current working directory to the given path.It returns None in all the cases.


3 Answers

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)
like image 60
Laurence Gonsalves Avatar answered Sep 29 '22 18:09

Laurence Gonsalves


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.

like image 28
u0b34a0f6ae Avatar answered Sep 29 '22 19:09

u0b34a0f6ae


__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?)

like image 23
shylent Avatar answered Sep 29 '22 18:09

shylent