What's the use of the else in try/except/else?

K

kj

I know about the construct:

try:
# do something
except ...:
# handle exception
else:
# do something else

....but I can't come with an example in which the same couldn't be
accomplished with

try:
# do something
# do something else
except ...:
# handle exception

The only significant difference I can come up with is that in the
second form, the except clause may be masking some unexpected
exceptions from the "do something else" part. Is this the rationale
behind this else clause? Or is there something more to it?

TIA!

kynn
 
K

kj

Yes, in a way. The idea of catching particular exceptions is to only
handle exceptions you expect (let the others go out to more general
reporters). So, not only should you choose the tightest exception to
catch that you can, but you should look for it in a very narrow window:
exactly where you expect it.
try:
v = mumble.field
except AttributeError:
pass
else:
sys.warning('field was actually there?')
as opposed to:
try:
v = mumble.field
sys.warning('field was actually there?')
except AttributeError:
pass
The idea is to make it clear what you expect might go
wrong that you are prepared to handle.

Wow. As rationales for syntax constructs go, this has got to be
the most subtle one I've ever seen...

Thanks!

kynn
 
L

Lawrence D'Oliveiro

I know about the construct:

try:
# do something
except ...:
# handle exception
else:
# do something else

...but I can't come with an example in which the same couldn't be
accomplished with [no else]

I'd agree. If you have to resort to a "try .. else", then might I
respectfully suggest that you're using exceptions in a way that's
complicated enough to get you into trouble.
 
S

Steven D'Aprano

I know about the construct:

try:
# do something
except ...:
# handle exception
else:
# do something else

...but I can't come with an example in which the same couldn't be
accomplished with [no else]

I'd agree. If you have to resort to a "try .. else", then might I
respectfully suggest that you're using exceptions in a way that's
complicated enough to get you into trouble.



try:
rsrc = get(resource)
except ResourceError:
log('no more resources available')
raise
else:
do_something_with(rsrc)
finally:
rsrc.close()



is complicated? It seems pretty straightforward to me.
 
S

Steven D'Aprano

I know about the construct:

try:
# do something
except ...:
# handle exception
else:
# do something else

...but I can't come with an example in which the same couldn't be
accomplished with [no else]

I'd agree. If you have to resort to a "try .. else", then might I
respectfully suggest that you're using exceptions in a way that's
complicated enough to get you into trouble.



try:
rsrc = get(resource)
except ResourceError:
log('no more resources available')
raise
else:
do_something_with(rsrc)
finally:
rsrc.close()



Except of course such a pattern won't work, because if get(resource)
fails, rsrc will not exist to be closed. So a better, and simpler,
example would be to drop the finally clause:

try:
rsrc = get(resource)
except ResourceError:
log('no more resources available')
raise
else:
do_something_with(rsrc)
rsrc.close()


To really be safe, that should become:

try:
rsrc = get(resource)
except ResourceError:
log('no more resources available')
raise
else:
try:
do_something_with(rsrc)
finally:
rsrc.close()


which is now starting to get a bit icky (but only a bit, and only because
of the nesting, not because of the else).
 
P

Peter Pearson

On 12 May 2009 09:35:36 GMT, Steven D'Aprano wrote:
[snip]
To really be safe, that should become:

try:
rsrc = get(resource)
except ResourceError:
log('no more resources available')
raise
else:
try:
do_something_with(rsrc)
finally:
rsrc.close()

Thanks, Steven. I find these examples illuminating.

Not trying to be dense, but given that the "except" block
re-raises the exception, isn't the above the same as . . .?

try:
rsrc = get(resource)
except ResourceError:
log('no more resources available')
raise
try:
do_something_with(rsrc)
finally:
rsrc.close()
 
C

Carl Banks

I know about the construct:
try:
    # do something
except ...:
    # handle exception
else:
    # do something else
...but I can't come with an example in which the same couldn't be
accomplished with [no else]
I'd agree. If you have to resort to a "try .. else", then might I
respectfully suggest that you're using exceptions in a way that's
complicated enough to get you into trouble.
try:
    rsrc = get(resource)
except ResourceError:
    log('no more resources available')
    raise
else:
    do_something_with(rsrc)
finally:
    rsrc.close()

Except of course such a pattern won't work, because if get(resource)
fails, rsrc will not exist to be closed. So a better, and simpler,
example would be to drop the finally clause:

Resource acquisition goes outside the try block. If you want to log
an error, then get() goes inside its own try block.

try:
rsrc = get(resource)
except ResourceError:
log('no more resources available')
raise
try:
do_something_with(rsrc)
finally:
rsrc.close()


If you hadn't reraised the exception, then the else clause would have
been useful as follows:

try:
rsrc = get(resource)
except ResourceError:
proceed_without_resource()
else:
try:
proceed_with_resource()
# Note: proceed_with_resource() can possibly raise
# ResourceError itself
finally:
rsrc.close()

The main reason for the else clause on try blocks is so that you don't
risk catching spurrious exceptions. You could stick
proceed_with_resource() in the try clause, but then if
proceed_with_resource() throws ResourceError because it tries to
acquire a different resource and fails, then it'd be caught and
proceed_without_resource() would be called, which is a mistake.

In general, you want to limit the contents of a try clause to code
that throws an exception you want to catch (if you're trying to catch
an exception); everything else should go into the else clause.

Incidentally, I can't think of any realistic use cases for using all
four of try...except...else...finally.


Carl Banks
 
G

greg

kj said:
Wow. As rationales for syntax constructs go, this has got to be
the most subtle one I've ever seen...

It's to avoid masking bugs. Suppose you accidentally
wrote

try:
v = mumble.field
sys.warming('field was actually there?')
except AttributeError:
pass

Then you could easily fail to notice that
you had written 'warming' instead of 'warning'.
 
M

ma

A really great use for try/except/else would be if an object is
implementing its own __getitem__ method, so you would have something
like this:

class SomeObj(object):
def __getitem__(self, key):
try:
#sometype of assertion here based on key type
except AssertionError, e:
raise TypeError, e #invalid type
else:
#continue processing, etc.. return some index, which will auto throw
#an index error if you have some type of indexable datastructure
 
S

Steven D'Aprano

A really great use for try/except/else would be if an object is
implementing its own __getitem__ method, so you would have something
like this:

class SomeObj(object):
def __getitem__(self, key):
try:
#sometype of assertion here based on key type
except AssertionError, e:
raise TypeError, e #invalid type

Why raise AssertionError only to catch it and raise TypeError? Why not
just raise TypeError in the first place?


If you're thinking of writing this:

assert isinstance(key, whatever)

you should be aware that when Python is run with the -O flag (optimize),
all asserts are disabled, and so your error checking code will not run
and your program will crash and burn in a horrible flaming mess.


[steve@wow-wow ~]$ python -O
Python 2.5 (r25:51908, Nov 6 2007, 16:54:01)
[GCC 4.1.2 20070925 (Red Hat 4.1.2-27)] on linux2
Type "help", "copyright", "credits" or "license" for more information.

The assert statement is not for runtime error checking. assert is for
checking your program logic and invariants. If you've ever written a
comment like:

# When we get here, then x will always be greater than 3.

(or something like that), that's a great candidate for an assertion:

assert x > 3, "x is unexpectedly less than or equal to three"


For error checking, you should do something like this:

if not isinstance(key, whatever):
raise ValueError

rather than:

try:
if not isinstance(key, whatever):
raise AssertionError
except AssertionError:
raise ValueError
 
M

ma

That's great to know! Thanks for that explanation, I am refactoring
something and I was going to make ample use of assertion as I thought
it was the same as C's assertion without the NDEBUG flag.


A really great use for try/except/else would be if an object is
implementing its own __getitem__ method, so you would have something
like this:

class SomeObj(object):
    def __getitem__(self, key):
              try:
                      #sometype of assertion here based on key type
              except AssertionError, e:
                      raise TypeError, e #invalid type

Why raise AssertionError only to catch it and raise TypeError? Why not
just raise TypeError in the first place?


If you're thinking of writing this:

assert isinstance(key, whatever)

you should be aware that when Python is run with the -O flag (optimize),
all asserts are disabled, and so your error checking code will not run
and your program will crash and burn in a horrible flaming mess.


[steve@wow-wow ~]$ python -O
Python 2.5 (r25:51908, Nov  6 2007, 16:54:01)
[GCC 4.1.2 20070925 (Red Hat 4.1.2-27)] on linux2
Type "help", "copyright", "credits" or "license" for more information.

The assert statement is not for runtime error checking. assert is for
checking your program logic and invariants. If you've ever written a
comment like:

# When we get here, then x will always be greater than 3.

(or something like that), that's a great candidate for an assertion:

assert x > 3, "x is unexpectedly less than or equal to three"


For error checking, you should do something like this:

if not isinstance(key, whatever):
   raise ValueError

rather than:

try:
   if not isinstance(key, whatever):
       raise AssertionError
except AssertionError:
   raise ValueError
 
A

Andre Engels

A really great use for try/except/else would be if an object is
implementing its own __getitem__ method, so you would have something
like this:

class SomeObj(object):
   def __getitem__(self, key):
               try:
                       #sometype of assertion here based on key type
               except AssertionError, e:
                       raise TypeError, e #invalid type
               else:
                       #continue processing, etc.. return some index, which will auto throw
                       #an index error if you have some type of indexable datastructure

Again, this is a case where no else is needed. Whenever you raise
something in the except clause (as your only exit point), it would
work just as well (though it might be a bit uglier) to put the rest
after the try ... except without an else. It is when after the
exception execution continues normally, but skipping part of the code,
that you need an else.
 
B

Beni Cherniavsky

[Long mail. You may skip to the last paragraph to get the summary.]

To really be safe, that should become:

try:
    rsrc = get(resource)
except ResourceError:
    log('no more resources available')
    raise
else:
    try:
        do_something_with(rsrc)
    finally:
        rsrc.close()

which is now starting to get a bit icky (but only a bit, and only because
of the nesting, not because of the else).
Note that this example doesn't need ``else``, because the ``except``
clause re-raises the exception. It could as well be::

try:
rsrc = get(resource)
except ResourceError:
log('no more resources available')
raise
try:
do_something_with(rsrc)
finally:
rsrc.close()

``else`` is relevant only if your ``except`` clause(s) may quietly
suppress the exception::

try:
rsrc = get(resource)
except ResourceError:
log('no more resources available, skipping do_something')
else:
try:
do_something_with(rsrc)
finally:
rsrc.close()

And yes, it's icky - not because of the ``else`` but because
aquisition-release done correctly is always an icky pattern. That's
why we now have the ``with`` statement - assuming `get()` implements a
context manager, you should be able to write::

with get(resource) as rsrc:
do_something_with(rsrc)

But wait, what if get() fails? We get an exception! We wanted to
suppress it::

try:
with get(resource) as rsrc:
do_something_with(rsrc)
except ResourceError:
log('no more resources available, skipping do_something')

But wait, that catches ResourceError in ``do_something_with(rsrc)`` as
well! Which is precisely what we tried to avoid by using
``try..else``!
Sadly, ``with`` doesn't have an else clause. If somebody really
believes it should support this pattern, feel free to write a PEP.

I think this is a bad example of ``try..else``. First, why would you
silently suppress out-of-resource exceptions? If you don't suppress
them, you don't need ``else``. Second, such runtime problems are
normally handled uniformely at some high level (log / abort / show a
message box / etc.), wherever they occur - if ``do_something_with(rsrc)
`` raises `ResourceError` you'd want it handled the same way.

So here is another, more practical example of ``try..else``:

try:
bar = foo.get_bar()
except AttributeError:
quux = foo.get_quux()
else:
quux = bar.get_quux()

assuming ``foo.get_bar()`` is optional but ``bar.get_quux()`` isn't.
If we had put ``bar.get_quux()`` inside the ``try``, it could mask a
bug. In fact to be precise, we don't want to catch an AttributeError
that may happen during the call to ``get_bar()``, so we should move
the call into the ``else``::

try:
get_bar = foo.get_bar
except AttributeError:
quux = foo.get_quux()
else:
quux = get_bar().get_quux()

Ick!

The astute reader will notice that cases where it's important to
localize exception catching involves frequent excetions like
`AttributeError` or `IndexError` -- and that these cases are already
handled by `getattr` and `dict.get` (courtesy of Guido's Time
Machine).

Bottom line(s):
1. ``try..except..else`` is syntactically needed only when ``except``
might suppress the exception.
2. Minimal scope of ``try..except`` doesn't always apply (for
`AttirbuteError` it probably does, for `MemoryError` it probably
doesn't).
3. It *is* somewhat ackward to use, which is why the important use
cases - exceptions that are frequently raised and caught - deserve
wrapping by functions like `getattr()` with default arguments.
 
L

Lawrence D'Oliveiro

In message <8dc983db-b8c4-4897-
And yes, it's icky - not because of the ``else`` but because
aquisition-release done correctly is always an icky pattern.

Only in the presence of exceptions.
 

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

Staff online

Members online

Forum statistics

Threads
474,008
Messages
2,570,270
Members
46,872
Latest member
Stephendes

Latest Threads

Top