[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.