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

Beni Cherniavsky beni.cherniavsky at gmail.com
Sun May 17 10:26:04 EDT 2009


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

On May 12, 12:35 pm, Steven D'Aprano wrote:
> 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.



More information about the Python-list mailing list