There is a shorter, and more idiomatic way of achieving the same result than the code you propose.
You do try/catch for the error you anticipate, and then check the condition on the error object inside the except clause. The trick is to re-raise the same error object unchanged if the error object is not of the particular sub-type you were expecting.
import errno
try:
action()
except IOError as ioe:
if ioe.errno not in (errno.ENOENT,):
# this re-raises the same error object.
raise
pass # ENOENT. that line is optional, but it makes it look
# explicit and intentional to fall through the exception handler.
It's important that you re-raise the initial error with just raise
, without parameters. You may be tempted re-raise with raise ioe
instead of just raise
on that line. Even though you would preserve the same error message, if you used raise ioe
it would make the error's stack trace look as if the error happened on that line, and not inside action()
where it really happened.
In your proposed code, your second exception handler (i.e. except e
), even though it is syntactically valid, will not trigger. You have to specify except <type>:
, or except <type> as <variable>:
.
If you wanted to do additional sophisticated error handling, to ensure correctness, you could nest all of the previous try/catch in an outer try
/except
:
try:
try:
action()
except IOError as ioe:
if ioe.errno not in (errno.ENOENT,): raise
except IOError as any_other_ioerror:
# this is reached in case you get other errno values
sophisticated_error_handling()
except OtherExceptionTypeICareAbout as other:
other_fancy_handler()
One thing you want to be mindful of, when nesting exception handlers like that, is that exception handlers can raise exceptions too. There's a tutorial on exception handling in the docs here, but you may find it a bit dense.
Method #2
If you're into meta-python tricks, you could consider an approach using the with statement. You could create a python context manager which absorbs particular types of errors.
# add this to your utilities
class absorb(object):
def __init__(self, *codes):
self.codes = codes
def __exit__(self, exc_type, value, traceback):
if hasattr(value, "errno") and getattr(value, "errno") in self.codes:
return True # exception is suppressed
return False # exception is re-raised
def __enter__(self):
return None
And then you can write simple code like:
# if ENOENT occurs during the block, abort the block, but do not raise.
with absorb(errno.ENOENT):
delete_my_file_whether_it_exists_or_not()
print("file deleted.") # reachable only if previous call returned
If you still want to have sophisticated error handling for other errno cases, you would include the above in a try/except, like so:
try:
with absorb(errno.ENOENT): action()
# the code here is reachable only if action() returns,
# or if one of the suppressed/absorbed exceptions has been raised (i.e. ENOENT)
except IOError as any_other_ioerror:
# any exception with errno != ENOENT will come here
sophisticated_error_handling()
Note: you may also want to take a look at contextlib, which might eliminate some of the meta-gore for you.