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

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

Related

In python 3, re-raise an error with a shorter traceback

I'm trying to create a try-except block that re-raises an arbitrary exception, but only includes the final block in the traceback stack.
Something like this:
import traceback
def my_func(shorten_tracebacks=True):
try:
# Do stuff here
except Exception as e:
if shorten_tracebacks:
raise TheSameTypeOfError, e, traceback.print_exc(limit=1)
else:
raise(e)
Is there a preferred way to do this?
In case it matters, I'm doing this for convenience in debugging certain APIs that are often used in jupyter notebooks---they tend to generate really long stack traces where only the last block is informative. This forces the user to scroll a lot. If you don't want to shorten the traceback, you can always set shorten_tracebacks=False
My preference is to create a new exception without a context (if an exception has a context python will print both the exception and the exception which caused that exception, and the exception which caused that exception, and so on...)
try:
...
except:
raise Exception("Can't ramistat the foo, is foo installed?") from None
Some best practices:
Include relevant debugging information in the exception message.
Use a custom exception type, so callers can catch the new exception.
Catch only the specific error types you're expecting, to let unexpected errors fall through with the extended traceback.
The downside to this approach is that if your exception catching is overly broad, you can end up suppressing useful context which is important to debugging the exception. An alternate pattern might look like this:
try:
...
except Exception as e:
if "ramistat not found" in e.message:
# The ramistat is missing. This is a common kind of error.
# Create a more helpful and shorter message
raise Exception("Can't ramistat the foo, is foo installed?") from None
else:
# Some other kind of problem
raise e
Essentially, you check the exception, and replace it with a custom message if it's a kind of error you know how to deal with. Otherwise, you re-raise the original exception, and let the user figure out what to do.

Python 3 - dynamically catch unknown number of exceptions [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.

How to raise exception within a try/except block properly

right now I have a problem where I want to raise a specific TypeError if there is one. However, what ends up happening is the interpreter sees the first error, and then in the middle of handling it it raises the other one as well saying "During handling of the above exception, another exception occurred:"
this is what I have
def function(dictionary)
try:
value = max(dictionary.values())
except TypeError:
raise TypeError("some error")
I plug in the following into the shell:
function({1:'a', 2:3})
How can I approach this?
If you want to discard the exception context, you can explicitly discard it using from None, e.g.:
try:
value = max(dictionary.values())
except TypeError:
raise TypeError("some error") from None
That said, it's usually best to leave the context in place; the only time you'll see it is if the exception is uncaught and the default logging occurs, or you try to log the exception (e.g. with logger.exception). That additional information is often useful, especially for extremely broad exception types like TypeError and ValueError (where you intend to catch specific known subtypes, and unexpectedly catch one caused in a completely different way).
To be clear, this only works on Python 3, but then, exception context chaining only exists on Python 3; on Python 2, the context is lost automatically.
Since you are raising the exception while handling it, the exception is sent back to the caller function.
If you just want to handle it and print the error and move on with rest of the execution, you can do sth like this
except TypeError as t:
print ("Error", t)

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.

raising error does not prevent try-except clause to get executed?

I've got surprised while testing a piece of code that looked a bit like that:
if x:
try:
obj = look-for-item-with-id==x in a db
if obj is None:
# print debug message
raise NotFound('No item with this id')
return obj
except Exception, e:
raise Error(e.message)
I expected that if there was no item with a provided id (x) in a db, the NotFound exception would be raised. But instead, after getting to if clause and printing debug message it gets to the except clause and raises the Exception (exc message is Item not found...). Could someone be so kind and enlighten me here?
When you say except Exception, e:, you are explicitly catching (almost) any exception that might get raised within that block -- including your NotFound.
If you want the NotFound itself to propagate further up, you don't need to have a try/except block at all.
Alternatively, if you want to do something specific when you detect the NotFound but then continue propagating the same exception, you can use a blank raise statement to re-raise it instead of raising a new exception like you're doing; something like:
try:
# .. do stuff
if blah:
raise NotFound(...)
except NotFound, e:
# print something
raise
Also note that I've changed the exception block to except NotFound -- it's generally not a good idea to use except Exception because it catches everything, which can hide errors you may not have expected. Basically, you want to use except to catch specific things which you know how to handle right there.
if obj is array please check if length or count of items is zero
this mean obj not none but not contain items

Categories