Python 3 - dynamically catch unknown number of exceptions [duplicate] - python

I know that I can do:
try:
# do something that may fail
except:
# do this if ANYTHING goes wrong
I can also do this:
try:
# do something that may fail
except IDontLikeYouException:
# say please
except YouAreTooShortException:
# stand on a ladder
But if I want to do the same thing inside two different exceptions, the best I can think of right now is to do this:
try:
# do something that may fail
except IDontLikeYouException:
# say please
except YouAreBeingMeanException:
# say please
Is there any way that I can do something like this (since the action to take in both exceptions is to say please):
try:
# do something that may fail
except IDontLikeYouException, YouAreBeingMeanException:
# say please
Now this really won't work, as it matches the syntax for:
try:
# do something that may fail
except Exception, e:
# say please
So, my effort to catch the two distinct exceptions doesn't exactly come through.
Is there a way to do this?

From Python Documentation:
An except clause may name multiple exceptions as a parenthesized tuple, for example
except (IDontLikeYouException, YouAreBeingMeanException) as e:
pass
Or, for Python 2 only:
except (IDontLikeYouException, YouAreBeingMeanException), e:
pass
Separating the exception from the variable with a comma will still work in Python 2.6 and 2.7, but is now deprecated and does not work in Python 3; now you should be using as.

How do I catch multiple exceptions in one line (except block)
Do this:
try:
may_raise_specific_errors():
except (SpecificErrorOne, SpecificErrorTwo) as error:
handle(error) # might log or have some other default behavior...
The parentheses are required due to older syntax that used the commas to assign the error object to a name. The as keyword is used for the assignment. You can use any name for the error object, I prefer error personally.
Best Practice
To do this in a manner currently and forward compatible with Python, you need to separate the Exceptions with commas and wrap them with parentheses to differentiate from earlier syntax that assigned the exception instance to a variable name by following the Exception type to be caught with a comma.
Here's an example of simple usage:
import sys
try:
mainstuff()
except (KeyboardInterrupt, EOFError): # the parens are necessary
sys.exit(0)
I'm specifying only these exceptions to avoid hiding bugs, which if I encounter I expect the full stack trace from.
This is documented here: https://docs.python.org/tutorial/errors.html
You can assign the exception to a variable, (e is common, but you might prefer a more verbose variable if you have long exception handling or your IDE only highlights selections larger than that, as mine does.) The instance has an args attribute. Here is an example:
import sys
try:
mainstuff()
except (KeyboardInterrupt, EOFError) as err:
print(err)
print(err.args)
sys.exit(0)
Note that in Python 3, the err object falls out of scope when the except block is concluded.
Deprecated
You may see code that assigns the error with a comma. This usage, the only form available in Python 2.5 and earlier, is deprecated, and if you wish your code to be forward compatible in Python 3, you should update the syntax to use the new form:
import sys
try:
mainstuff()
except (KeyboardInterrupt, EOFError), err: # don't do this in Python 2.6+
print err
print err.args
sys.exit(0)
If you see the comma name assignment in your codebase, and you're using Python 2.5 or higher, switch to the new way of doing it so your code remains compatible when you upgrade.
The suppress context manager
The accepted answer is really 4 lines of code, minimum:
try:
do_something()
except (IDontLikeYouException, YouAreBeingMeanException) as e:
pass
The try, except, pass lines can be handled in a single line with the suppress context manager, available in Python 3.4:
from contextlib import suppress
with suppress(IDontLikeYouException, YouAreBeingMeanException):
do_something()
So when you want to pass on certain exceptions, use suppress.

From Python documentation -> 8.3 Handling Exceptions:
A try statement may have more than one except clause, to specify
handlers for different exceptions. At most one handler will be
executed. Handlers only handle exceptions that occur in the
corresponding try clause, not in other handlers of the same try
statement. An except clause may name multiple exceptions as a
parenthesized tuple, for example:
except (RuntimeError, TypeError, NameError):
pass
Note that the parentheses around this tuple are required, because
except ValueError, e: was the syntax used for what is normally
written as except ValueError as e: in modern Python (described
below). The old syntax is still supported for backwards compatibility.
This means except RuntimeError, TypeError is not equivalent to
except (RuntimeError, TypeError): but to except RuntimeError as
TypeError: which is not what you want.

If you frequently use a large number of exceptions, you can pre-define a tuple, so you don't have to re-type them many times.
#This example code is a technique I use in a library that connects with websites to gather data
ConnectErrs = (URLError, SSLError, SocketTimeoutError, BadStatusLine, ConnectionResetError)
def connect(url, data):
#do connection and return some data
return(received_data)
def some_function(var_a, var_b, ...):
try: o = connect(url, data)
except ConnectErrs as e:
#do the recovery stuff
blah #do normal stuff you would do if no exception occurred
NOTES:
If you, also, need to catch other exceptions than those in the
pre-defined tuple, you will need to define another except block.
If you just cannot tolerate a global variable, define it in main()
and pass it around where needed...

One of the way to do this is..
try:
You do your operations here;
......................
except(Exception1[, Exception2[,...ExceptionN]]]):
If there is any exception from the given exception list,
then execute this block.
......................
else:
If there is no exception then execute this block.
and another way is to create method which performs task executed by except block and call it through all of the except block that you write..
try:
You do your operations here;
......................
except Exception1:
functionname(parameterList)
except Exception2:
functionname(parameterList)
except Exception3:
functionname(parameterList)
else:
If there is no exception then execute this block.
def functionname( parameters ):
//your task..
return [expression]
I know that second one is not the best way to do this, but i'm just showing number of ways to do this thing.

As of Python 3.11 you can take advantage of the except* clause that is used to handle multiple exceptions.
PEP-654 introduced a new standard exception type called ExceptionGroup that corresponds to a group of exceptions that are being propagated together. The ExceptionGroup can be handled using a new except* syntax. The * symbol indicates that multiple exceptions can be handled by each except* clause.
For example, you can handle multiple exceptions
try:
raise ExceptionGroup('Example ExceptionGroup', (
TypeError('Example TypeError'),
ValueError('Example ValueError'),
KeyError('Example KeyError'),
AttributeError('Example AttributeError')
))
except* TypeError:
...
except* ValueError as e:
...
except* (KeyError, AttributeError) as e:
...
For more details see PEP-654.

Related

KeyError not caught [duplicate]

I know that I can do:
try:
# do something that may fail
except:
# do this if ANYTHING goes wrong
I can also do this:
try:
# do something that may fail
except IDontLikeYouException:
# say please
except YouAreTooShortException:
# stand on a ladder
But if I want to do the same thing inside two different exceptions, the best I can think of right now is to do this:
try:
# do something that may fail
except IDontLikeYouException:
# say please
except YouAreBeingMeanException:
# say please
Is there any way that I can do something like this (since the action to take in both exceptions is to say please):
try:
# do something that may fail
except IDontLikeYouException, YouAreBeingMeanException:
# say please
Now this really won't work, as it matches the syntax for:
try:
# do something that may fail
except Exception, e:
# say please
So, my effort to catch the two distinct exceptions doesn't exactly come through.
Is there a way to do this?
From Python Documentation:
An except clause may name multiple exceptions as a parenthesized tuple, for example
except (IDontLikeYouException, YouAreBeingMeanException) as e:
pass
Or, for Python 2 only:
except (IDontLikeYouException, YouAreBeingMeanException), e:
pass
Separating the exception from the variable with a comma will still work in Python 2.6 and 2.7, but is now deprecated and does not work in Python 3; now you should be using as.
How do I catch multiple exceptions in one line (except block)
Do this:
try:
may_raise_specific_errors():
except (SpecificErrorOne, SpecificErrorTwo) as error:
handle(error) # might log or have some other default behavior...
The parentheses are required due to older syntax that used the commas to assign the error object to a name. The as keyword is used for the assignment. You can use any name for the error object, I prefer error personally.
Best Practice
To do this in a manner currently and forward compatible with Python, you need to separate the Exceptions with commas and wrap them with parentheses to differentiate from earlier syntax that assigned the exception instance to a variable name by following the Exception type to be caught with a comma.
Here's an example of simple usage:
import sys
try:
mainstuff()
except (KeyboardInterrupt, EOFError): # the parens are necessary
sys.exit(0)
I'm specifying only these exceptions to avoid hiding bugs, which if I encounter I expect the full stack trace from.
This is documented here: https://docs.python.org/tutorial/errors.html
You can assign the exception to a variable, (e is common, but you might prefer a more verbose variable if you have long exception handling or your IDE only highlights selections larger than that, as mine does.) The instance has an args attribute. Here is an example:
import sys
try:
mainstuff()
except (KeyboardInterrupt, EOFError) as err:
print(err)
print(err.args)
sys.exit(0)
Note that in Python 3, the err object falls out of scope when the except block is concluded.
Deprecated
You may see code that assigns the error with a comma. This usage, the only form available in Python 2.5 and earlier, is deprecated, and if you wish your code to be forward compatible in Python 3, you should update the syntax to use the new form:
import sys
try:
mainstuff()
except (KeyboardInterrupt, EOFError), err: # don't do this in Python 2.6+
print err
print err.args
sys.exit(0)
If you see the comma name assignment in your codebase, and you're using Python 2.5 or higher, switch to the new way of doing it so your code remains compatible when you upgrade.
The suppress context manager
The accepted answer is really 4 lines of code, minimum:
try:
do_something()
except (IDontLikeYouException, YouAreBeingMeanException) as e:
pass
The try, except, pass lines can be handled in a single line with the suppress context manager, available in Python 3.4:
from contextlib import suppress
with suppress(IDontLikeYouException, YouAreBeingMeanException):
do_something()
So when you want to pass on certain exceptions, use suppress.
From Python documentation -> 8.3 Handling Exceptions:
A try statement may have more than one except clause, to specify
handlers for different exceptions. At most one handler will be
executed. Handlers only handle exceptions that occur in the
corresponding try clause, not in other handlers of the same try
statement. An except clause may name multiple exceptions as a
parenthesized tuple, for example:
except (RuntimeError, TypeError, NameError):
pass
Note that the parentheses around this tuple are required, because
except ValueError, e: was the syntax used for what is normally
written as except ValueError as e: in modern Python (described
below). The old syntax is still supported for backwards compatibility.
This means except RuntimeError, TypeError is not equivalent to
except (RuntimeError, TypeError): but to except RuntimeError as
TypeError: which is not what you want.
If you frequently use a large number of exceptions, you can pre-define a tuple, so you don't have to re-type them many times.
#This example code is a technique I use in a library that connects with websites to gather data
ConnectErrs = (URLError, SSLError, SocketTimeoutError, BadStatusLine, ConnectionResetError)
def connect(url, data):
#do connection and return some data
return(received_data)
def some_function(var_a, var_b, ...):
try: o = connect(url, data)
except ConnectErrs as e:
#do the recovery stuff
blah #do normal stuff you would do if no exception occurred
NOTES:
If you, also, need to catch other exceptions than those in the
pre-defined tuple, you will need to define another except block.
If you just cannot tolerate a global variable, define it in main()
and pass it around where needed...
One of the way to do this is..
try:
You do your operations here;
......................
except(Exception1[, Exception2[,...ExceptionN]]]):
If there is any exception from the given exception list,
then execute this block.
......................
else:
If there is no exception then execute this block.
and another way is to create method which performs task executed by except block and call it through all of the except block that you write..
try:
You do your operations here;
......................
except Exception1:
functionname(parameterList)
except Exception2:
functionname(parameterList)
except Exception3:
functionname(parameterList)
else:
If there is no exception then execute this block.
def functionname( parameters ):
//your task..
return [expression]
I know that second one is not the best way to do this, but i'm just showing number of ways to do this thing.
As of Python 3.11 you can take advantage of the except* clause that is used to handle multiple exceptions.
PEP-654 introduced a new standard exception type called ExceptionGroup that corresponds to a group of exceptions that are being propagated together. The ExceptionGroup can be handled using a new except* syntax. The * symbol indicates that multiple exceptions can be handled by each except* clause.
For example, you can handle multiple exceptions
try:
raise ExceptionGroup('Example ExceptionGroup', (
TypeError('Example TypeError'),
ValueError('Example ValueError'),
KeyError('Example KeyError'),
AttributeError('Example AttributeError')
))
except* TypeError:
...
except* ValueError as e:
...
except* (KeyError, AttributeError) as e:
...
For more details see PEP-654.

Handle a specific exception (say, ENOENT) separately from others

In Python, specific POSIX error conditions do not have their separate exception types — they are distinguished by an attribute inside of the OSError exception object.
Let's imagine I'm performing a file operation (removing a possibly inexistent file over SFTP) and I want to ignore ENOENT, but still handle any other error or exception. Is it possible to do that more elegantly than as following?
try:
action()
except OSError as e:
if e.errno == errno.ENOENT:
pass
else:
sophisticated_error_handling(e)
except e:
sophisticated_error_handling(e)
I dislike this method because it involves repetition.
Note: there is no X-Y problem. The "action" is a library function and it cannot be told to ignore ENOENT.
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.
Next by the except just enter the error type!
test = [1, 2, 3, 4, 5, 6, 'aasd']
for i in range(50):
try:
print(test[i])
except IndexError:
pass

The best way to determine exception type

I have an exception instance and need to execute code depending on it's type. Which way is more clearly - re raise exception or isinstance check?
re raise:
try:
raise exception
except OperationError as err:
result = do_something1(err)
except (InvalidValue, InvalidContext) as err:
result = do_something2(err)
except AnotherException as err:
result = do_something3(err)
except:
pass
isinstance check:
if isinstance(exception, OperationError):
result = do_something1(err)
elif isinstance(exception, (InvalidValue, InvalidContext)):
result = do_something2(err)
elif isinstance(exception, AnotherException):
result = do_something3(err)
PS. Code is used in django process_exception middleware, therefore when re-raising exception I should write except:pass for all unknown exceptions.
First get rid of the except: pass clause - one should never silently pass exceptions, specially in a bare except clause (one should never use a bare except clause anyway).
This being said, the "best" way really depends on concrete use case. In your above example you clearly have different handlers for different exceptions / exceptions sets, so the obvious solution is the first one. Sometimes you do have some code that's common to all or most of the handlers and some code that's specific to one exception or exceptions subset, then you may want to use isinstance for the specific part, ie:
try:
something_that_may_fail()
except (SomeException, SomeOtherException, YetAnotherOne) as e:
do_something_anyway(e)
if isinstance(e, YetAnotherOne):
do_something_specific_to(e)
Now as mkrieger commented, having three or more exceptions to handle may be a code or design smell - the part in the try block is possibly doing too many things - but then again sometimes you don't have much choice (call to a builtin or third-part function that can fail in many different ways...).
You could store the exceptions you want to handle as keys in a dictionary with different functions as their values. Then you can catch all errors in just one except and call the dictionary to make sure the relevant function is run.
error_handler = {
OperationError: do_something1,
InvalidValue: do_something2,
InvalidContext: do_something2,
AnotherException: do_something3,
}
try:
#raise your exception
except (OperationError, InvalidValue, InvalidContext, AnotherException) as err:
result = error_handler[type(err)]()
I suspect there might be a way to programmatically pass error_handler.keys() to except, but the means I've tried in Python2.7 have not worked so far.
Note that as martineau points out, because this uses type(err) as a dictionary key it won't handle derived exception classes the way that isinstance(err, ...) and except (err) would. You'd need to match exact exceptions.

Can exceptions be caught from both a try and except block with one except statement? [duplicate]

I have some code in a Python except clause that is intended to do some logging, but the logging code might itself cause an exception. In my case, I'd like to just ignore any second exception that might occur, and raise the original exception. Here is a very simplified example:
try:
a = this_variable_doesnt_exist
except:
try:
1/0
except:
pass
raise
Running the above code, I hope to get:
NameError: name 'this_variable_doesnt_exist' is not defined
but instead, in Python 2.x, I get:
ZeroDivisionError: integer division or modulo by zero
I've discovered that in Python 3.x, it does what I want.
I couldn't find much commentary on this in the Python 2.x docs (unless I missed it). Can I achieve this in 2.x?
I believe what you're seeing is the result of exception chaining, which is a change in Python 3.
From the Motivation section of the PEP:
During the handling of one exception (exception A), it is possible that another exception (exception B) may occur. In today's Python (version 2.4), if this happens, exception B is propagated outward and exception A is lost. In order to debug the problem, it is useful to know about both exceptions. The __context__ attribute retains this information automatically.
The PEP then goes on to describe the new exception chaining (which is implemented in Py3k) in detail—it's an interesting read. I learned something new today.
With abstraction:
def log_it():
try:
1 / 0
except:
pass
try:
this = that
except:
log_it()
raise
Does what you want in Python 2.5
Another way to do it is to store the exception in a variable, then re-raise it explicitly:
try:
this = that
except NameError, e: # or NameError as e for Python 2.6
try:
1 / 0
except:
pass
raise e
Note that you probably shouldn't just be using a bare except to catch everything that might come - it's usually best to catch the specific exceptions you expect to occur in case a drastic and fatal exception (like being out of memory) occurs.
In my CausedException class I take care of that for Python 2.x (and for Python 3 as well, in case you want to pass cause trees instead of simple cause chains). Maybe it can help you.

Deciding which exceptions to catch in Python

Suppose that I am using a library X that specifies for example that exception.BaseError is the base class for all exceptions of X.
Now, there is another exception, say X.FooError, which of course inherits from exception.BaseError but is more generalized, let's say that it handles invalid input. Let's suppose there are many other such classes, inherting from BaseError but all for generalized cases.
X
|
BaseError
|
FooError
So I want to then check for invalid input. So which exception should I catch? Of course, catching each individual exception is not possible, so I catch the X.BaseError and then print an error message. Or I can catch the X.FooError specifically but then I miss out all the other error cases.
Is this the standard way of doing it -- catch the base exception? If yes, then why do the other exceptions exist? For the generalized case when we want to catch a specific exception?
Catch only the exceptions you can handle. If you can handle both the base exception and the derived exception then catch both. But make sure to put the derived exception first, since the first exception handler found that matches is the one used.
try:
X.foo()
except X.FooError:
pass
except X.BaseError:
pass
As usual, there is good advice in PEP-8, Style Guide for Python Code:
When catching exceptions, mention specific exceptions whenever possible instead of using a bare except: clause.
There's lots more in there, but it's pointless me reproducing it here.
In this case, I would catch the specifics, were I to handle them differently to a BaseError and the BaseError for those that require more general handling. I would stop well short of catching the built-in Exception, however.
You catch a specfic exception by defining it in the except clause, thus:
try:
#do stuff
except X.FooError:
# handle the error
except (X.AnotherError, x.YetAnotherError), exc:
print 'I'm handling %s' % exc
Notice you can handle multiple exception classes by listing them in a tuple.

Categories