Why no try-except-finally ?

K

KefX

This may have been discussed before, but I'm kind of confused as to why Python
doesn't support having both an except ~and~ a finally clause, like this:

try:
raise RuntimeException
except:
print "What is the answer?"
finally:
print 42

This doesn't work because I use both 'except' and 'finally'. I'm not saying
that it SHOULD work, I'm wondering why it doesn't. I thought about it for a
second, though, and came up with the ad-hoc "nested-try idiom", as I call it:

try:
try:
raise RuntimeException
except:
print "What is the answer?"
finally:
print 42

This works as expected: the exception gets thrown, the question gets asked in
the 'except' clause, and then the question gets answered in the 'finally'
clause. Of course in real-world code we wouldn't be asking questions and giving
answers via exception-handling, but the path of execution is what counts. Is
this idiom unPythonic somehow? (The nested-try thing does look odd...) Or is it
the way we're supposed to do it? Or is there something I'm missing?

- Kef
 
K

KefX

This may have been discussed before, but I'm kind of confused as to why
Python
doesn't support having both an except ~and~ a finally clause, like this:

try:
raise RuntimeException
except:
print "What is the answer?"
finally:
print 42

This doesn't work because I use both 'except' and 'finally'. I'm not saying
that it SHOULD work, I'm wondering why it doesn't. I thought about it for a
second, though, and came up with the ad-hoc "nested-try idiom", as I call it:

try:
try:
raise RuntimeException
except:
print "What is the answer?"
finally:
print 42

This works as expected: the exception gets thrown, the question gets asked in
the 'except' clause, and then the question gets answered in the 'finally'
clause. Of course in real-world code we wouldn't be asking questions and
giving
answers via exception-handling, but the path of execution is what counts. Is
this idiom unPythonic somehow? (The nested-try thing does look odd...) Or is
it
the way we're supposed to do it? Or is there something I'm missing?

- Kef

I messed up a couple things in my post, heh. First off, of course
RuntimeException should be RuntimeError. The other is that this (the nested try
idiom):
try:
try:
raise RuntimeException
except:
print "What is the answer?"
finally:
print 42

should instead be this:

try:
try:
raise RuntimeError
except:
print "What is the answer?"
raise # <- HAD BEEN FORGOTTEN
finally:
print 42

- Kef
 
F

Frithiof Andreas Jensen

this idiom unPythonic somehow? (The nested-try thing does look odd...) Or is it
the way we're supposed to do it? Or is there something I'm missing?

According to the documentation the purpose of a try...finally statement is
to ensure that aquired ressources will be freed again as in

self.lock.acquire()
try:
..do whatever we are supposed to do;
...it may fail here or in a different module etc.
...exceptions may be caught or not
finally:
...but we always end *here*
...and uncaught exceptions are re-raised from here too (i think)
...so that "the right thing" happens
self.lock.release()

so that the ressource is released regardless of any exceptions etc. that
happens between the try: and the finally:

The separation is probably because the purpose of try..finally is different
from exception handling and mixing the two would obscure that (or maybe
there is some wizardry under the hood).
 
K

KefX

The separation is probably because the purpose of try..finally is different
from exception handling and mixing the two would obscure that (or maybe
there is some wizardry under the hood).

I dunno. Other languages such as Java support "try-catch-finally" (or
"try-except-finally" as we're calling it), and I don't see how the lack of
restrictions does anything bad like, say, reduce the clarity of code.

I bring this up because I originally wrote this in my game code:

try:
PlayGame()
except:
err_msg = "FATAL ERROR: " + sys.exc_info()[0]
logger.critical(err_msg)
finally:
DeInit()

In other words, if something went wrong, an error message would be printed out
to the log, and then in either case, my game would try to exit gracefully. Of
course, rewriting it without the finally: is no big deal (just write DeInit()
in the except block and again after the block)...in this case. What if I wanted
to execute a bunch of lines? Code duplication is bad. Well, I could write a
local function and just have it call THAT in both cases, but that felt like
overkill. Thus, I came up with the 'nested-try idiom':

try:
try:
PlayGame()
except:
err_msg = "FATAL ERROR: " + sys.exc_info()[0]
logger.critical(err_msg)
raise
finally:
DeInit()

But it looks kind of ugly. But it's still easy to just change the statements in
the 'finally' block if needed. But the nested-try looks strange, and worse, you
have to remember to put the 'raise' in the except block (remember I forgot to
do this in my original post!), and error-handling code tends to be the
least-tested...

I dunno. More thoughts?

- Kef
 
E

Erik Max Francis

KefX said:
I dunno. Other languages such as Java support "try-catch-finally" (or
"try-except-finally" as we're calling it), and I don't see how the
lack of
restrictions does anything bad like, say, reduce the clarity of code.

The claim is that some user polling indicated that newbies didn't really
understand how the try...except...finally syntax was supposed to work,
and thus it was eliminated for the sake of clarity. I'm not sure I buy
the argument, but that's why.
 
D

Duncan Booth

(e-mail address removed) (KefX) wrote in

I bring this up because I originally wrote this in my game code:

try:
PlayGame()
except:
err_msg = "FATAL ERROR: " + sys.exc_info()[0]
logger.critical(err_msg)
finally:
DeInit()

In other words, if something went wrong, an error message would be
printed out to the log, and then in either case, my game would try to
exit gracefully. Of course, rewriting it without the finally: is no
big deal (just write DeInit() in the except block and again after the
block)...in this case. What if I wanted to execute a bunch of lines?
Code duplication is bad.

Hang on, there is something wrong with what you say here. If you really had
the code you wrote above, and you put DeInit() in the except block and
again after it then any time an exception was thrown you would call DeInit
twice. Is this *really* what you wanted?

This code would reliably call DeInit every time, whether or not an
exception was thrown, without code duplication:

try:
PlayGame()
except:
err_msg = "FATAL ERROR: " + sys.exc_info()[0]
logger.critical(err_msg)
DeInit()
 
D

Duncan Booth

(e-mail address removed) (KefX) wrote in

This may have been discussed before, but I'm kind of confused as to
why Python doesn't support having both an except ~and~ a finally
clause, like this:

try:
raise RuntimeException
except:
print "What is the answer?"
finally:
print 42

This doesn't work because I use both 'except' and 'finally'. I'm not
saying that it SHOULD work, I'm wondering why it doesn't.

The first thing to ask yourself when wondering why something doesn't work
in Python, is exactly what you expect it should do.

The 'try' block can exit normally; by throwing an exception that is caught
by an 'except'; or by throwing an exception that is not caught and
propogates outside. Do you want the 'finally' block executed in all of
those cases, or only for some of them?


The answer to your question, given by Tim Peters in
http://groups.google.com/groups?selm=LNBBLJKPBEHFEDALKOLCMEONGIAA.tim_one%4
0email.msn.com
is that Python used to allow this, and people got caught out because it
didn't work the way everyone expected. By writing try/except/else and
try/finally separately you can clearly communicate what you expect to
happen.
 
K

KefX

Hang on, there is something wrong with what you say here. If you really had
the code you wrote above, and you put DeInit() in the except block and
again after it then any time an exception was thrown you would call DeInit
twice. Is this *really* what you wanted?

Err, no, it'd also exit with an error code in the except block (something I
hadn't written in at that moment because I had already realized it wasn't going
to work that way). My bad.

For now I rewrote it like this, just a modified version of what you suggested:

err_code = 0
try:
game.Play() # was PlayGame() before
except:
err_msg = "FATAL ERROR: " + sys.exc_info()[0]
logger.critical(err_msg)
err_code = 1

DeInit()
return err_code
 
P

Peter Hansen

KefX said:
For now I rewrote it like this, just a modified version of what you suggested:

err_code = 0
try:
game.Play() # was PlayGame() before
except:
err_msg = "FATAL ERROR: " + sys.exc_info()[0]
logger.critical(err_msg)
err_code = 1

DeInit()
return err_code

Somewhat better style, generally, IMHO, would be to replace "err_code = 1"
with "raise" in the above. Consider returning error codes to be a
parallel (and somewhat obsolescent) way of communicating failures to
calling code. Raising an exception should be generally preferred.

Note, however, that doing this (re-raising the exception) brings
you back to your original query as well, as DeInit() would not get
called if it were not in a try/finally.

A second style point: use a general try/except (without specifying
the exceptions to be caught) is usually a Very Bad Idea. It's okay
at the top level of a module, but it will mask all kinds of bugs
at the lower levels. For example, in the above you might have
misspelled Play() as play() and you would get an AttributeError,
but would think that it was the call to Play() which had raised
an exception and the "FATAL ERROR".

Again, however, restricting the set of exceptions called means that
in the case where one of the unhandled ones is raised (unhandled at
this particular level, anyway), you still want a finally clause
to ensure DeInit() is executed.

I'd say, in response to the original question, that the nested
try/except in the try/finally is the appropriate, preferred Pythonic
idiom.

-Peter
 
J

JCM

KefX said:
This may have been discussed before, but I'm kind of confused as to why Python
doesn't support having both an except ~and~ a finally clause,

I miss this as well.
 
M

MetalOne

I look at it this way. You are either handling exceptions or you are
letting them propagate up the stack. If you handle them, then there
is no need for the finally as you will remain in the function and you
can release resources at the bottom of the try/except block.

try: pass
except: pass
#release resources here

If you are letting all exceptions propagate up the stack then there is
no except clause
try: pass
finally: #release resources

Of course there may be cases where some exceptions are handled and
others are not. Or an exception is partially handled, and then
rethrown. In these cases, it seems a try/catch/finally idiom would be
useful.

I believe that Delphi works the same way as Python in this regard.
 
K

KefX

Somewhat better style, generally, IMHO, would be to replace "err_code = 1"
with "raise" in the above. Consider returning error codes to be a
parallel (and somewhat obsolescent) way of communicating failures to
calling code. Raising an exception should be generally preferred.

I agree with you in general, but this code is in my main() function, and
returning an error code one way or another is the only way to report the error
status to the OS. :)

Instead of this:

if __name__ == '__main__':
main()

my code does this:

if __name == '__main__':
sys.exit(main())

I believe Guido suggested that idiom in his blog.

- Kef
 
P

Peter Hansen

KefX said:
I agree with you in general, but this code is in my main() function, and
returning an error code one way or another is the only way to report the error
status to the OS. :)

Instead of this:

if __name__ == '__main__':
main()

my code does this:

if __name == '__main__':
sys.exit(main())

I believe Guido suggested that idiom in his blog.

Ah, then I would suggest moving the code that requires proper cleanup
(the finally part) down a level, and simplify the top level so that
all it does is catch all exceptions and convert to an error value
which it then returns.

Works out to about the same thing as saying "nest the try clauses"
but at least it has the advantage of higher cohesion in the two
separate code areas.

-Peter
 

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

No members online now.

Forum statistics

Threads
473,743
Messages
2,569,478
Members
44,899
Latest member
RodneyMcAu

Latest Threads

Top