How to handle exception chaining raised from the except block - python

In my example I have a custom exception class MyCustomException and in main I divide an integer a by zero which raises a ZeroDivisionError exception. With the except block I catch ZeroDivisionError and then raise MyCustomException from err; this creates a chained exception, my own, plus the one in err.
Now how can I catch chained exceptions or how do chained exceptions work? Python doen't let me to catch MyCustomException in my code with except block.
class MyCustomException(Exception):
pass
a=10
b=0
reuslt=None
try:
result=a/b
except ZeroDivisionError as err:
print("ZeroDivisionError -- ",err)
raise MyCustomException from err
except MyCustomException as e:
print("MyException",e) # unable to catch MyCustomException
The output I get when I execute it:
ZeroDivisionError -- division by zero
Traceback (most recent call last):
File "python", line 13, in <module>
MyCustomException

Using raise in the except clause won't search for exception handlers in the same try block (it did not occur in that try block).
It will search for handlers one level up , that is, an outer try block. If that isn't found it'll interrupt execution as it normally does (resulting in the exception being displayed).
In short, you need an enclosing try in the outer level with the appropriate except MyCustomException in order to catch your custom exception:
try:
try:
result=a/b
except ZeroDivisionError as err:
print("ZeroDivisionError -- ",err)
raise MyCustomException from err
except MyCustomException as e:
print("Caught MyException", e)
Which, when executed, now prints out:
ZeroDivisionError -- division by zero
Caught MyException

Related

Replace a Python exception with another one

I expect a python function I use to raise a KeyError, in case the user of my script entered a wrong value.
When this happens, I want to raise a ValueError, not a KeyError. I tried to catch the KeyError, then raise a ValueError instead. I then get both.
For example:
def func(my_dict):
return my_dict["my_key"]
a = {}
try:
func(a)
except KeyError as e:
raise ValueError("Replaced exception")
results in this:
$ python3 except.py
Traceback (most recent call last):
File "except.py", line 7, in <module>
func(a)
File "except.py", line 2, in func
return my_dict["my_key"]
KeyError: 'my_key'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "except.py", line 9, in <module>
raise ValueError("Replaced exception")
ValueError: Replaced exception
I don't care about the KeyError itself, I just want to tell the user about the ValueError.
How can I catch the KeyError, but discard it? Is it bad practice? How should I organize this instead?
One way, described here, is to raise ... from None
def func(my_dict):
return my_dict["my_key"]
a = {}
try:
func(a)
except KeyError as e:
raise ValueError("Replaced exception") from None
But I'm still not sure if this is a good idea or not.
What you stumbled upon is the difference between an exceptions cause and it's context. There are three slightly different things you can do, and each have different semantics:
Version 1:
try:
raise KeyError()
except KeyError as ex:
raise ValueError()
This will show both trackbacks separated by the line
During handling of the above exception, another exception occurred:
This is saying: There was one exception, and in the catch block there was another (unrelated, usually unexpected) exception. This is not what you want here.
Version 2:
try:
raise KeyError()
except KeyError as ex:
raise ValueError() from None
This will just show the traceback for the ValueError. This might be what you want, if you are really sure you never ever have to debug the KeyError. Otherwise, read on.
Version 3
try:
raise KeyError()
except KeyError as ex:
raise ValueError() from ex
Again, this will show both tracebacks, this time separated by the line
The above exception was the direct cause of the following exception:
Notice the difference from Version 1? Semantically this says: On this level of abstraction this is a ValueError. But in case you need to debug it, here's the traceback of the original KeyError, too. Pretty neat, don't you think?
You don't have a try/except block to manage the ValueError. So, what you're seeing is a stack trace that's telling you where the ValueError originated.
Consider this:
def func(my_dict):
return my_dict["my_key"]
a = {}
try:
try:
func(a)
except KeyError as e:
raise ValueError("Replaced exception")
except ValueError as e:
print(e)
Output:
Replaced exception
i.e.,
No stack trace because you've caught the ValueError

Catch exception by __cause__

Python 3 allows raising exceptions from other exceptions e.g.:
try:
raise CustomException()
except CustomException as e:
try:
raise TypeError() from e
except TypeError as e:
print(type(e.__cause__))
The CustomException instance is stored in the exception object's __cause__ attribute.
The code above should print CustomException.
Is there a way to catch the original exception instead of the newly raised one?
try:
raise CustomException()
except CustomException as e:
try:
raise TypeError() from e
except CustomException as e:
print(type(e)) # should reach here
Overriding __subclasscheck__ does not work since I don't have access to the instance and I it is impossible to specify that CustomException is a subclass of all classes or of the cause class.
Is there a way to trick Python into thinking that the exception we're catching is of the type of __cause__?
If you have control over the exception that is raised you can perhaps make it a subclass of the raised exception:
try:
raise TypeError()
except TypeError as e:
try:
class CustomException(TypeError.__class__):
pass
raise CustomException() from e
except TypeError as e:
print(type(e)) # Reaches here
That said, the mechanism of catch-and-reraise is meant to hide what the original exception was so that later code doesn't depend on implementation details.
Simple solution: catch all exceptions and filter required:
try:
raise ZeroDivisionError()
except ZeroDivisionError as e:
try:
raise TypeError() from e
except Exception as ex:
if type(ex.__cause__) is ZeroDivisionError:
print('ZeroDivisionError')
else:
raise # re-raise exception if it has different __cause__

python: Exception flow: Continue to down catch block after catching?

I am curious if there is a way in python to continue on within try/catch block, after you catch an exception, look at its properties, and if not relevant, then continue down the stack.
try:
# Code
except AppleError as apple_ex:
# look at 'apple_ex.error_code' error body, and if not relevant,
# continue on to next down the catch block...
# In other words, proceed to except BananaError and so on down.
except BananaError as banana_ex:
# ...
except Exception as ex:
# ...
That is not how exceptions are handled in Python. When you raise an exception in a try block, if you handle catching it in the except, it will fall inside that block, but will not continue to the next except at that same level. Observe this functional example:
try:
raise AttributeError()
except AttributeError:
raise TypeError()
except TypeError:
print("it got caught") # will not catch the TypeError raised above
So, in your try, we raise an AttributeError, we catch it, and then raise a TypeError inside catching the AttributeError.
The except TypeError will not catch that TypeError.
Based on how you are explaining your problem, you need to rethink how you are handling your exceptions and see if you can determine the handling of errors somewhere else, and raise the error there.
For example:
def some_func():
try:
thing()
except SomeException:
# analyze the exception here and raise the error you *should* raise
if apple_error_thing:
raise AppleError
elif banana_error_thing:
raise BananaError
else:
raise UnknownException
def your_func():
try:
some_func()
except AppleError as e:
print('Apple')
except BananaError as e:
print('Banana')
except UnknownException as e:
print('Unknown')
An AppleError is still an AppleError and not a BananaError, even if error_code is not relevant, so it makes no sense to fall through to BananaError.
You could instead define specific errors for your different error codes:
GRANNY_SMITH_ERROR = 1
MACINTOSH_ERROR = 2
class AppleError(Exception):
def __init__(self, error_code, *args):
super(AppleError, self).__init__(*args)
self.error_code = error_code
class GrannySmithError(AppleError):
def __init__(self, *args):
super(GrannySmithError, self).__init__(GRANNY_SMITH_ERROR, *args)
class MacintoshError(AppleError):
def __init__(self, *args):
super(MacintoshError, self).__init__(MACINTOSH_ERROR, *args)
Then you can try to match the specific error:
try: raise MacintoshError()
except MacintoshError as exc: print("mac")
except GrannySmithError as exc: print("granny smith")
If you do not care to distinguish between different types of apple errors, you can still trap all apple errors:
try: raise MacintoshError()
except AppleError as exc: print("generic apple")
You can combine these, for example, only doing special processing for GrannySmith, not for Macintosh:
try: raise MacintoshError()
except GrannySmithError as exc: print("granny smith")
except AppleError as exc: print("generic apple")
The important thing is to list the errors from most specific to least specific. If you test for AppleError before GrannySmithError, then it will never enter the GrannySmith block.
No, that isn't possible. After the exception is handled by the inner except it doesn't have the ability to get handled by the outer except:
From the docs on the try statement:
When the end of this block is reached, execution continues normally after the entire try statement. (This means that if two nested handlers exist for the same exception, and the exception occurs in the try clause of the inner handler, the outer handler will not handle the exception.)
In short your only solution might be to have another handler at an outer level and re-raise the exception in the inner handler, that is:
try:
try:
raise ZeroDivisionError
except ZeroDivisionError as e:
print("caught")
raise ZeroDivisionError
except ZeroDivisionError as f:
print("caught")
Now the nested except raises an exception which is consequently caught by a similar handler.

Exception raised in both try and except .

def FancyDivide(list_of_numbers, index):
try:
try:
raise Exception("0")
finally:
denom = list_of_numbers[index]
for i in range(len(list_of_numbers)):
list_of_numbers[i] /= denom
except Exception, e:
print e
When function is called I got the following output.
FancyDivide([0, 2, 4], 0)
integer division or modulo by zero
In the try code an exception is raised. In finally also there is an exception .Why is it so that exception in the finally was caught not the exception in the try.
From the documentation -
A finally clause is always executed before leaving the try statement, whether an exception has occurred or not. When an exception has occurred in the try clause and has not been handled by an except clause (or it has occurred in a except or else clause), it is re-raised after the finally clause has been executed.
(Emphasis mine)
As given, the exception - Exception("0") would have only been raised again after completing the finally block. But because an exception occured in the finally block, it was raised instead of the Exception("0") .

Boolean 'not' in Python exception catching

I try to construct a except clause that catches everything except [sic] a particular type of exception:
try:
try:
asdjaslk
except not NameError as ne: #I want this block to catch everything except NameError
print("!NameError- {0}: {1}".format(ne.__class__, ne))
except Exception as e: #NameError is the only one that should get here
print("Exception- {0}: {1}".format(e.__class__, e))
The language accepts the not in the except clause, but does nothing with it:
>>> Exception- <type 'exceptions.NameError'>: name 'asdjaslk' is not defined
Is it possible to do this or should I reraise them?
You'll have to re-raise. An except statement can only whitelist, not blacklist.
try:
asdjaslk
except Exception as ne:
if isinstance(ne, NameError):
raise
The not NameError expression returns False, so you are essentially trying to catch:
except False:
but no exception will ever match a boolean instance.
The syntax allows for any valid Python expression, and the thrown exception is matched against the outcome of that expression. except SomeException if debug else SomeOtherException: is perfectly valid, for example.
You can can try this:
try:
# your code raising exceptions
except NameError:
# catch the exception you don't want to catch
# but immediately raise it again:
print("re-raising NameError- {0}: {1}".format(ne.__class__, ne))
raise
except Exception as e:
# handle all other exceptions here
print("catching Exception- {0}: {1}".format(e.__class__, e))
pass

Categories

Resources