Handling both specific and general Python exceptions? - python

I would like to catch a specific exception and handle it accordingly - then I would like to continue and perform the generic handling that other exceptions would have to.
Coming from a C background, I could have previously utilised gotos to achieve the desired effect.
This is what I'm currently doing is and it works fine:
try:
output_var = some_magical_function()
except IntegrityError as ie:
integrity_error_handling()
shared_exception_handling_function(zde) # could be error reporting
except SomeOtherException as soe:
shared_exception_handling_function(soe) # the same function as above
Tldr:
Ie - is there "Pythonic" way of doing the following:
try:
output_var = some_magical_function()
except IntegrityError as ie:
integrity_error_handling()
except ALLExceptions as ae: # all exceptions INCLUDING the IntregityError
shared_exception_handling_function(ae) # could be error reporting
NB: I am aware of the finally clause - this isn't intended for tidy-up (ie- closing files)ยท

You could reraise the exception, and handle the generic case in the outer handler of a nested setup:
try:
try:
output_var = some_magical_function()
except IntegrityError as zde:
integrity_error_handling()
raise
except ALLExceptions as ae: # all exceptions INCLUDING the IntregityError
shared_exception_handling_function(ae) # could be error reporting
The unqualified raise statement re-raises the current exception, so the IntegrityError exception is thrown again to be handled by the AllExceptions handler.
The other path you could take is to test for the exception type:
try:
output_var = some_magical_function()
except ALLExceptions as ae: # all exceptions INCLUDING the IntregityError
if isinstance(ae, IntegrityError):
integrity_error_handling()
shared_exception_handling_function(ae) # could be error reporting

The Exception class will match all exceptions...
try:
output_var = some_magical_function()
except IntegrityError as zde:
integrity_error_handling()
except Exception as ae:
shared_exception_handling_function(ae) # could be error reporting
But it sounds like you want the final clause to run for both IntegrityError exceptions as well as everything else. So you'll need a different construct, possibly this:
try:
try:
output_var = some_magical_function()
except IntegrityError as zde:
integrity_error_handling()
raise
except Exception as ae:
shared_exception_handling_function(ae) # could be error reporting
The raise command on the inner try...except block causes the
caught exception to be passed up to the outer block.

Related

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.

How to handle exception chaining raised from the except block

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

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

Handling all but one exception

How to handle all but one exception?
try:
something
except <any Exception except for a NoChildException>:
# handling
Something like this, except without destroying the original traceback:
try:
something
except NoChildException:
raise NoChildException
except Exception:
# handling
The answer is to simply do a bare raise:
try:
...
except NoChildException:
# optionally, do some stuff here and then ...
raise
except Exception:
# handling
This will re-raise the last thrown exception, with original stack trace intact (even if it's been handled!).
New to Python ... but is not this a viable answer?
I use it and apparently works.... and is linear.
try:
something
except NoChildException:
assert True
except Exception:
# handling
E.g., I use this to get rid of (in certain situation useless) return exception FileExistsError from os.mkdir.
That is my code is:
try:
os.mkdir(dbFileDir, mode=0o700)
except FileExistsError:
assert True
and I simply accept as an abort to execution the fact that the dir is not somehow accessible.
I'd offer this as an improvement on the accepted answer.
try:
dosomestuff()
except MySpecialException:
ttype, value, traceback = sys.exc_info()
raise ttype, value, traceback
except Exception as e:
mse = convert_to_myspecialexception_with_local_context(e, context)
raise mse
This approach improves on the accepted answer by maintaining the original stacktrace when MySpecialException is caught, so when your top-level exception handler logs the exception you'll get a traceback that points to where the original exception was thrown.
You can do type checking on exceptions! Simply write
try:
...
except Exception as e:
if type(e) == NoChildException:
raise
It still includes the original stack trace.
I found a context in which catching all errors but one is not a bad thing, namely unit testing.
If I have a method:
def my_method():
try:
something()
except IOError, e:
handle_it()
Then it could plausibly have a unit test that looks like:
def test_my_method():
try:
my_module.my_method()
except IOError, e:
print "shouldn't see this error message"
assert False
except Exception, e:
print "some other error message"
assert False
assert True
Because you have now detected that my_method just threw an unexpected exception.

python: recover exception from try block if finally block raises exception

Say I have some code like this:
try:
try:
raise Exception("in the try")
finally:
raise Exception("in the finally")
except Exception, e:
print "try block failed: %s" % (e,)
The output is:
try block failed: in the finally
From the point of that print statement, is there any way to access the exception raised in the try, or has it disappeared forever?
NOTE: I don't have a use case in mind; this is just curiosity.
I can't find any information about whether this has been backported and don't have a Py2 installation handy, but in Python 3, e has an attribute called e.__context__, so that:
try:
try:
raise Exception("in the try")
finally:
raise Exception("in the finally")
except Exception as e:
print(repr(e.__context__))
gives:
Exception('in the try',)
According to PEP 3314, before __context__ was added, information about the original exception was unavailable.
try:
try:
raise Exception("in the try")
except Exception, e:
print "try block failed"
finally:
raise Exception("in the finally")
except Exception, e:
print "finally block failed: %s" % (e,)
However, It would be a good idea to avoid having code that is likely to throw an exception in the finally block - usually you just use it to do cleanup etc. anyway.

Categories

Resources