Best way to handle exceptions with try/finally

  • Thread starter Carl J. Van Arsdall
  • Start date
C

Carl J. Van Arsdall

Hey python people,

I'm interested in using the try/finally clause to ensure graceful
cleanup regardless of how a block of code exits. However, I still am
interested in capturing the exception.

The scenario is that I have an object that accesses a global memory
space accessible via multiple threads. When shared resources are
accessed a lock is acquired and then released. In the case that someone
tries to access a variable that doesn't exist, I still need to release
the lock so that other threads can continue their work, but an exception
should be raised to the caller.

Anyhow, I was playing with ideas in my code, and in this attempt I
attempted to nest "try"s to at least make sure that accessing variables
was raising an exception. That's the case, and then I attempt to raise
the exception "ValueError" so that the calling function will get the
exception. This doesn't work though. It seems that any exceptions get
caught up in that "try/finally" and i can't get the exception to raise
higher than that without raising it outside of this try. Is the best
thing to do to have the exception mechanism set some type of flag that I
can check during the "finally" statement?

##CODE##

class Shared:
def __init__(self):
self.__userData= {}
self.__mutex = threading.Lock() #lock object

def getVar(self, variableName):
temp = None
error = 0
self.__mutex.acquire() #accessing shared dictionary
try:
try:
temp = self.__userData[variableName]
except:
print "Variable doesn't exist in shared
space"
raise ValueError
finally:
self.__mutex.release()
return temp

def putVar(self, variableName, value):
self.__mutex.acquire() #accessing shared dictionary
try:
self.__userData[variableName] = value
finally:
self.__mutex.release()
return

--

Carl J. Van Arsdall
(e-mail address removed)
Build and Release
MontaVista Software
 
M

Maxim Sloyko

I guess the following standard method will help :

class MyLocker(object):
def __init__(self, lock):
self.lock = lock
self.lock.acquire()

def __del__(self):
self.lock.release()

Then whenever you need to acquire a lock:
templock = MyLocker(self.__mutex)

del templock # will release the lock (provided you didn't create an
extra link to this object)
 
R

Roy Smith

Carl J. Van Arsdall said:
class Shared:
def __init__(self):
self.__userData= {}
self.__mutex = threading.Lock() #lock object

def getVar(self, variableName):
temp = None
error = 0
self.__mutex.acquire() #accessing shared dictionary
try:
try:
temp = self.__userData[variableName]
except:
print "Variable doesn't exist in shared space"
raise ValueError
finally:
self.__mutex.release()
return temp

A few comments on this.

First, it's almost always a bad idea to have a bare "except:", because that
catches *every* possible exception. You want to catch as specific an
exception as you can (in this case, I suspect that's KeyError).

Second, I'm not sure if your intent was to return from the function or to
raise an exception on an unknown variableName. You can't do both! Quoting
from http://docs.python.org/ref/try.html..
When an exception occurs in the try clause, the exception is temporarily
saved, the finally clause is executed, and then the saved exception is
re-raised. If the finally clause raises another exception or executes a
return or break statement, the saved exception is lost.

so with the code you posted (from sight; I haven't tried running it), what
will happen is:

1) __userData[variableName] raises KeyError

2) The KeyError is caught by the inner try block and the except block runs,
which in turn raises ValueError

3) The ValueError is caught by the outer try block and the finally block
runs, releasing the mutex, discarding the ValueError exception, and
returning temp

4) Whoever called getVar() will see a normal return, with a return value of
None (because that's what temp was set to in the first line of getVar()
 
T

Thomas Guettler

Am Tue, 23 May 2006 12:47:05 -0700 schrieb Carl J. Van Arsdall:
Hey python people,

I'm interested in using the try/finally clause to ensure graceful
cleanup regardless of how a block of code exits. However, I still am
interested in capturing the exception.

You can reraise the exception:

try:
something()
except:
cleanup()
raise # reraise the caught exception
 
R

Roy Smith

"Maxim Sloyko said:
I guess the following standard method will help :

class MyLocker(object):
def __init__(self, lock):
self.lock = lock
self.lock.acquire()

def __del__(self):
self.lock.release()

Then whenever you need to acquire a lock:
templock = MyLocker(self.__mutex)

del templock # will release the lock (provided you didn't create an
extra link to this object)

Warning! Danger, Will Robinson! Evil space aliens are trying to write C++
in Python! Quick, we must get back to the ship before they eat our brains!

The problem here is that there is no guarantee that __del__() will get
called at any predictable time (if at all). C++ uses deterministic object
destruction. Python doesn't destroy (finalize?) objects until the garbage
collector runs.

See PEP-343 (http://www.python.org/dev/peps/pep-0343/) for the new "with"
statement which should solve problems like this. If I understand things
right, "with" will be included in Python-2.5, which is due out Real Soon
Now.
 
B

bruno at modulix

Carl J. Van Arsdall wrote:
(snip)

Not an answer to your question, just a few comments on your code:
class Shared:

class Shared(object):

def __init__(self):
self.__userData= {}
self.__mutex = threading.Lock() #lock object

Don't use __names unless you have a *really* strong reason to do so.
'protected' attributes are usually noted as _name.
def getVar(self, variableName):
temp = None
error = 0
self.__mutex.acquire() #accessing shared dictionary
try:
try:
temp = self.__userData[variableName]
except:

Don't use bare except clauses. Specify the exceptions you intend to
handle, let the other propagate.

I once spent many hours trying to debug a badly written module that has
such a catch-all clause with a misleading error message saying some file
could not be opened when the actual problem was with a wrong login/pass
for a connection.
print "Variable doesn't exist in shared
space"

stdout is for normal program outputs. Error messages should go to stderr.
raise ValueError

Should be a LookupError subclass. And the message should go with the
exception:
raise MyLookupErrorSubclass("Variable '%s' doesn't exist in shared
space" % variableName)

finally:
self.__mutex.release()
return temp

def putVar(self, variableName, value):
self.__mutex.acquire() #accessing shared dictionary
try:
self.__userData[variableName] = value
finally:
self.__mutex.release()
return

The return statement is useless here.

My 2 cents.
 
Z

Zameer

Doing cleaup in except is not the Python way. This is what finally is
for. Using except you would have to at least say:

try:
stuff()
cleanup()
except:
cleanup()
raise

Duplicate code - not nice. finally is the Python way:

try:
stuff()
finally:
cleanup()

That's it. But the return statement should not be a part of finally if
you want exceptions to propagate out of the function containing
try/finally. As mentioned multiple times in the thread.
 
C

Carl J. Van Arsdall

Zameer said:
That's it. But the return statement should not be a part of finally if
you want exceptions to propagate out of the function containing
try/finally. As mentioned multiple times in the thread.
Ah, great, that was it. Thanks to everyone for their help, I got a lot
of really useful information from your replies. I tend to put "return"
statements at the end of functions to make an attempt at being clean. I
realize that a lot of the time functions will just return but I was
hoping by explicitly stating my function returns that another person
reading my code would more easily see any exit points in my code. Turns
out that it came to bite me later.

Again, thanks to all your help and comments on my coding style.

..c

--

Carl J. Van Arsdall
(e-mail address removed)
Build and Release
MontaVista Software
 
Z

Zameer

I tend to put "return"
statements at the end of functions to make an attempt at being clean. I
realize that a lot of the time functions will just return but I was
hoping by explicitly stating my function returns that another person
reading my code would more easily see any exit points in my code. Turns
out that it came to bite me later.

You can put the return statement at the end.

try:
stuff()
finally:
cleanup()
return

The return statement is not necessary in this case, but it's not in the
finally part either. If you like it, keep it. Just don't make it part
of finally if you want exceptions to propagate.

I can see where you are going by making the return statement part of
finally, and I actually expected it to work the same as if it was not
part of finally. Turns out it doesn't. Can somebody please comment on
how this works in other languages? I know that finally is not a part of
standard C++ but Borland and Microsoft compilers allow use of the
keyword __finally. Delphi also has finally and I seem to recall that
returning from the finally part does not prevent the exception from
propagating, but memory may not serve me right.

Thank you for "raising" this quite interesting question.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,149
Members
46,695
Latest member
StanleyDri

Latest Threads

Top