Try-except code block needs optimization - python

At below, I have a try-except block that I want to refactor it. As you see, it is not pythonic and not-maintainable.
try:
try:
foo()
except xError:
doSth()
raise
except:
exc_type = sys.exc_info()[0]
if exc_type == FooError:
doBlaBla()
else:
doFlaFla()
blablabla() # whatever the exceptions is, run this code block
foobar()
zoo()
...
I change that block as the below code;
try:
try:
foo()
except xError:
doSth()
raise
except FooError:
doBlaBla()
raise
except:
doFlaFla()
raise
blablabla() # This is where the problem happens for me.
foobar()
zoo()
...
As you can see, I need a except-finally like operation. It will not run when no exception raises but any exceptions. What do you advice ? How should I change this clode block ?

Why you've used innder try exempt block?
In my opinion, it is a good practice to put finally except statement, that will catch unexpected error.
So I suggest:
try:
fn()
except Exception1:
do stuff you want
except Exception 2:
do another stuff
except Exception as e:
# Here you will catch an unexpected error, and you can log it
raise UnknownError -> do stuff you want

Why not something like:
def others():
"""Other stuff to do when non-xError occurs."""
blablabla()
foobar()
zoo()
and then factor into a single try:
try:
foo()
except xError:
doSth()
raise
except FooError:
doBlaBla()
others()
except Exception:
doFlaFla()
others()
A bare except is usually a bad idea.

You can wrap your exception-code in a try-finally block, for example:
try:
try:
foo()
except xError:
doSth()
raise
# catch Exception to avoid catching system errors
except Exception as e:
try:
if isinstance(e, FooError):
doBlaBla()
else:
doFlaFla()
raise
finally:
# this will always run regardless of what you raise
Another approach could be something like this:
e = None
try:
try:
foo()
except xError:
doSth()
raise
except FooError as e:
doBlaBla()
except Exception as e:
doFlaFla()
raise
finally:
# this will always run regardless of what you raise
if e:
# exception took place...

Related

How to print Exceptions of a nested loop?

I want to print all the exception statements from within the inner try to the catch of the outside try. Is there any way to do this without changing the inner try-catch block
def test_nested_exceptions():
try:
try:
raise AssertionError('inner error ')
except AssertionError as ae:
raise AssertionError("error in except")
finally:
raise AssertionError("error in finally")
except AssertionError as e:
print(e)
You can't access the error object in the finally block but you could get some details using the sys module as shown below.
import sys
def test_nested_exceptions():
try:
try:
raise AssertionError('inner error ')
except AssertionError as ae:
print(ae)
raise AssertionError("error in except")
finally:
print(sys.exc_info())
raise AssertionError("error in finally")
except AssertionError as e:
print(e)
test_nested_exceptions()

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.

Python nested try/except - raise first exception?

I'm trying to do a nested try/catch block in Python to print some extra debugging information:
try:
assert( False )
except:
print "some debugging information"
try:
another_function()
except:
print "that didn't work either"
else:
print "ooh, that worked!"
raise
I'd like to always re-raise the first error, but this code appears to raise the second error (the one caught with "that didn't work either"). Is there a way to re-raise the first exception?
raise, with no arguments, raises the last exception. To get the behavior you want, put the error in a variable so that you can raise with that exception instead:
try:
assert( False )
# Right here
except Exception as e:
print "some debugging information"
try:
another_function()
except:
print "that didn't work either"
else:
print "ooh, that worked!"
raise e
Note however that you should capture for a more specific exception rather than just Exception.
You should capture the first Exception in a variable.
try:
assert(False)
except Exception as e:
print "some debugging information"
try:
another_function()
except:
print "that didn't work either"
else:
print "ooh, that worked!"
raise e
raise by default will raise the last Exception.
raise raises the last exception caught unless you specify otherwise. If you want to reraise an early exception, you have to bind it to a name for later reference.
In Python 2.x:
try:
assert False
except Exception, e:
...
raise e
In Python 3.x:
try:
assert False
except Exception as e:
...
raise e
Unless you are writing general purpose code, you want to catch only the exceptions you are prepared to deal with... so in the above example you would write:
except AssertionError ... :

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.

Keeping try block small when catching exceptions in generator

How can I keep the try block as small as possible when I have to catch an exception which can occur in a generator?
A typical situation looks like this:
for i in g():
process(i)
If g() can raise an exception I need to catch, the first approach is this:
try:
for i in g():
process(i)
except SomeException as e:
pass # handle exception ...
But this will also catch the SomeException if it occurs in process(i) (this is what I do not want).
Is there a standard approach to handle this situation? Some kind of pattern?
What I am looking for would be something like this:
try:
for i in g():
except SomeException as e:
pass # handle exception ...
process(i)
(But this is syntactic nonsense of course.)
You could convert exceptions occurring in the inner block:
class InnerException(Exception):
pass
try:
for i in g():
try:
process(i)
except Exception as ex:
raise InnerException(ex)
except InnerException as ex:
raise ex.args[0]
except SomeException as e:
pass # handle exception ...
Another option is to write a local generator that wraps g:
def safe_g():
try:
for i in g():
yield i
except SomeException as e:
pass # handle exception ...
for i in safe_g():
process(i)
The straight-forward approach for this seems to be unwrap the for construct (which makes it impossible to catch exceptions just in the generator, due to its syntax) into its components.
gen = g()
while True:
try:
i = gen.next()
except StopIteration:
break
process(i)
Now we can just add our expected exception to the try block:
gen = g()
while True:
try:
i = gen.next()
except StopIteration:
break
except SomeException as e:
pass # handle exception ...
break
process(i)
Does that (besides it being ugly as hell) have disadvantages? And more: Is there a nicer solution?
(I won't accept my own answer because it being ugly, but maybe others like and upvote it.)
In your generator raise a different kind of exception, that you will be able to distinguish.
class GeneratorError(Exception):
pass
def g():
try:
yield <smth>
except:
raise GeneratorError
try:
for i in g():
process(i)
except GeneratorError:
pass # handle generator error
except SomeException as e:
pass # handle exception .
I don't know, if it works. You could evaluate g() into a list. I can't test it, because I don't have an iterator that throws an exception at hand.
try:
glist = list(g())
except SomeException as e:
pass # handle exception ...
for i in glist:
process(i)

Categories

Resources