Dynamically extend exception with custom exception in Python? - python

Let's say I have a special exception that does some neat and wonderful things - Solving world hunger, good will toward men, logging, etc:
class SpecialException(Exception):
# Does something really neat here.
pass
Now let's say that an exception may be raised, but we don't know what type of exception we'll encounter:
def crashAndBurn():
try:
import random
choice = random.choice([1,2])
if choice == 1:
int('asdf') # Can't cast string to int: ValueError.
else:
x # Variable `x` doesn't exist: NameError.
except Exception as e:
# Code to wrap `e` into `SpecialException` class
raise e
When that unknown type of exception is raised, we want to catch it, wrap it in our SpecialException class, and raise it so it can be caught either by the original type of exception thrown, or by catching SpecialException:
try:
crashAndBurn()
except ValueError as e:
print('This time it was a ValueError, and we want to handle this differently for some reason')
except SpecialException as e:
print('Handle this more generically')
Any recommendations on how to (reasonably) solve this?
In summary, we need:
Ability to catch exception originally raised, and ability to catch SpecialException
Retain traceback for easy debugging
Contain the original exception's error message
What I've Tried:
Tried using raise SpecialException from e. While we are able to view the error message and traceback from the originally raised exception, we are no longer able to catch it by the type of exception originally thrown... Eg: We can catch SpecialException, but can't catch ValueError:
def crashAndBurn():
try:
int('asdf') # ValueError
except Exception as e:
raise SpecialException('Special Exception Encountered').with_traceback(e.__traceback__) from e
try:
crashAndBurn()
except ValueError as e:
print('This will never be encountered')
except SpecialException as e:
print('This will be encountered, when we wanted it to be handled above')
The closest we've gotten technically fulfills our needs, however:
While the exception raised can be caught as either SpecialException or ValueError, it's actually raised as another, one-time use class: DynamicSpecialException
It's really gross and seemingly very un-pythonic
def crashAndBurn():
try:
int('asdf') # ValueError
except Exception as e:
class DynamicSpecialException(SpecialException, e.__class__):
pass # I feel dirty
raise DynamicSpecialException('Special Exception Encountered').with_traceback(e.__traceback__)
try:
crashAndBurn()
except ValueError as e:
print('Caught as a ValueError!')
try:
crashAndBurn()
except SpecialException as e:
print('Caught as SpecialException!')
What I was really expecting to find was something similar to raise e.extend(SpecialException) or raise SpecialException.from(e) - rather than this rabbit hole I've seemingly wiggled my way down today! :)

Here's a bit of a stab at it. It seems to do most of what you want, except that it appends the stack trace of the specialfactory handling.
The thing I learned is that you can't swap the exception class, e.__class__ = <dynamic class>, you have to create a new one and raise it.
import pdb
from traceback import print_exc as xp
import sys
def cpdb():
""" put `pdb` on commmand line to halt execution in debugger """
return "pdb" in sys.argv
class SpecialException(Exception):
def solve_world_hunger(self):
print(f"eat more 🦄")
def specialfactory(e):
""" creates a class dynamically and keeps the original as a base"""
cls_ = type("SpecialException", (SpecialException, e.__class__),{})
e2 = cls_(str(e))
e2.ori = e
e2.__dict__.update(**e.__dict__)
# 👇 you can try different flavors to see what changes:
# basically, I haven't found a way to suppress `The above exception was the direct cause of the following exception:`
# see also https://stackoverflow.com/questions/33809864/disable-exception-chaining-in-python-3
# return e2
# raise e2. raise specialfactory(e).with_traceback(e.__traceback__) from e
# raise e2 from e
raise e2.with_traceback(e.__traceback__) from e
def crashAndBurn(no_special=False, custom_message=None):
try:
if custom_message:
exc = ValueError(custom_message)
exc.foo = "bar"
raise exc
int('asdf') # ValueError
except Exception as e:
if no_special:
#just to investigate what things look like for a plain ValueError
raise
# raise specialfactory(e).with_traceback(e.__traceback__) from e
raise specialfactory(e) from e
#################################################################
# check what a regular unchanged ValueError looks like
#################################################################
try:
print("\n\n\n🔬regular ValueError, unchanged")
crashAndBurn(no_special=1)
except ValueError as e:
if cpdb(): pdb.set_trace()
print(f' plain ValueError: {e}')
xp()
except SpecialException as e:
if cpdb(): pdb.set_trace()
print(f' Caught as a SpecialException!: {e}')
xp()
#################################################################
# catch a Special as a ValueError
#################################################################
try:
print("\n\n\n🔬ValueError ")
crashAndBurn()
except ValueError as e:
if cpdb(): pdb.set_trace()
print(f' Caught as a ValueError! {e}')
xp()
except SpecialException as e:
if cpdb(): pdb.set_trace()
print(f' Caught as a SpecialException! {e}')
xp()
#################################################################
# catch a Special
#################################################################
try:
print("\n\n\n🔬SpecialException handling")
crashAndBurn()
except SpecialException as e:
if cpdb(): pdb.set_trace()
print(f' Caught as a SpecialException! {e} {e.solve_world_hunger()}')
xp()
except ValueError as e:
if cpdb(): pdb.set_trace()
print(f' Caught as a ValueError! {e}')
xp()
#################################################################
# custom variables are still available
#################################################################
try:
print("\n\n\n🔬ValueError with custom_message/content ")
crashAndBurn(custom_message="my custom_message")
except SpecialException as e:
if cpdb(): pdb.set_trace()
print(f' Caught as a SpecialException! {e} {e.foo=} {e.solve_world_hunger()}')
xp()
except ValueError as e:
if cpdb(): pdb.set_trace()
print(f' Caught as a ValueError! {e}')
xp()
output:
Traceback (most recent call last):
File "test_183.py", line 57, in <module>
crashAndBurn(no_special=1)
File "test_183.py", line 41, in crashAndBurn
int('asdf') # ValueError
ValueError: invalid literal for int() with base 10: 'asdf'
Traceback (most recent call last):
File "test_183.py", line 41, in crashAndBurn
int('asdf') # ValueError
ValueError: invalid literal for int() with base 10: 'asdf'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "test_183.py", line 74, in <module>
crashAndBurn()
File "test_183.py", line 47, in crashAndBurn
raise specialfactory(e) from e
File "test_183.py", line 30, in specialfactory
raise e2.with_traceback(e.__traceback__) from e
File "test_183.py", line 41, in crashAndBurn
int('asdf') # ValueError
SpecialException: invalid literal for int() with base 10: 'asdf'
Traceback (most recent call last):
File "test_183.py", line 41, in crashAndBurn
int('asdf') # ValueError
ValueError: invalid literal for int() with base 10: 'asdf'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "test_183.py", line 92, in <module>
crashAndBurn()
File "test_183.py", line 47, in crashAndBurn
raise specialfactory(e) from e
File "test_183.py", line 30, in specialfactory
raise e2.with_traceback(e.__traceback__) from e
File "test_183.py", line 41, in crashAndBurn
int('asdf') # ValueError
SpecialException: invalid literal for int() with base 10: 'asdf'
Traceback (most recent call last):
File "test_183.py", line 39, in crashAndBurn
raise exc
ValueError: my custom_message
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "test_183.py", line 108, in <module>
crashAndBurn(custom_message="my custom_message")
File "test_183.py", line 47, in crashAndBurn
raise specialfactory(e) from e
File "test_183.py", line 30, in specialfactory
raise e2.with_traceback(e.__traceback__) from e
File "test_183.py", line 39, in crashAndBurn
raise exc
SpecialException: my custom_message
🔬regular ValueError, unchanged
plain ValueError: invalid literal for int() with base 10: 'asdf'
🔬ValueError
Caught as a ValueError! invalid literal for int() with base 10: 'asdf'
🔬SpecialException handling
eat more 🦄
Caught as a SpecialException! invalid literal for int() with base 10: 'asdf' None
🔬ValueError with custom_message/content
eat more 🦄
Caught as a SpecialException! my custom_message e.foo='bar' None

Related

Custom Exception not caught in Python Flask App

I got a Flask app calling different modules. One of them eventually raises a NoPrice exception but the except NoPrice doesn't catch it as a NoPrice but except Exceptioncatches it...
class NoPrice(Exception):
pass
somewhere in a module
raise NoPrice('error')
in my flask app
try:
raise NoPrice('main')
except NoPrice as e:
print('main')
except Exception as e:
print('main2')
try:
resp = alicia.create_predictions(select)
except NoPrice as e:
print('main3')
except Exception as e:
print('main4')
print(repr(e))
print(e)
print(traceback.format_exc())
The output of this is
main
main4
NoPrice('No price found for material_reference 0148054b-e681-4fb6-9722-946f3cfa8529 the weight 1.7471266917293233', 'occurred at index 0')
('No price found for material_reference 0148054b-e681-4fb6-9722-946f3cfa8529 the weight 1.7471266917293233', 'occurred at index 0')
Traceback (most recent call last):
File "c/main.py", line 71, in create_predictions_api
resp = alicia.create_predictions(select)
File "/absolutepath/app/alicia.py", line 703, in create_predictions
self.set_machines_and_format()
File "/absolutepath/app/alicia.py", line 574, in set_machines_and_format
x["is_screenprinting_option"]), axis = 1)
File "/absolutepath/venv/lib/python3.7/site-packages/pandas/core/frame.py", line 6913, in apply
return op.get_result()
File "/absolutepath/venv/lib/python3.7/site-packages/pandas/core/apply.py", line 186, in get_result
return self.apply_standard()
File "/absolutepath/venv/lib/python3.7/site-packages/pandas/core/apply.py", line 292, in apply_standard
self.apply_series_generator()
File "/absolutepath/venv/lib/python3.7/site-packages/pandas/core/apply.py", line 321, in apply_series_generator
results[i] = self.f(v)
File "/absolutepath/app/alicia.py", line 574, in <lambda>
x["is_screenprinting_option"]), axis = 1)
File "/absolutepath/app/alicia.py", line 359, in predict_price_relying_on_format
output = Jerome(self.product, self.hipe_client_config).run(self.quote_input)
File "/absolutepath/app/jerome/jerome.py", line 235, in run
pliant = pliant_obj.price(quote_input, self.material_reference)
File "/absolutepath/app/jerome/options_construction/pliant.py", line 66, in price
quote_input['matter_margin_percentage'])
File "/absolutepath/app/jerome/options_construction/pliant.py", line 52, in pliant
raise NoPrice(f"No price found for material_reference {material_reference.id} the weight {x1}")
exceptions.NoPrice: ('No price found for material_reference 0148054b-e681-4fb6-9722-946f3cfa8529 the weight 1.7471266917293233', 'occurred at index 0')
Why doesn't the except NoPrice catch my exception ?
Shouldn't the ouput be
main
main3
My goal is ultimately to handle only my NoPrice exception
my_function must not be raising the NoPrice exception, and therefore it isn't caught in the 'main 3' section.

Custom exception error message using python

I'm trying to generate a custom exception message but get the below error -
import time
try:
start_time = time.time()
1/0
except Exception as ex:
elapsed_time = (time.time() - start_time)/60
e = "elapsed time(in mins) - {0}".format(elapsed_time)
print(type(ex))
raise ex(e)
Error:-
1/0
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "/Applications/PyCharm.app/Contents/helpers/pydev/_pydev_bundle/pydev_umd.py", line 197, in runfile
pydev_imports.execfile(filename, global_vars, local_vars) # execute the script
File "/Applications/PyCharm.app/Contents/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
exec(compile(contents+"\n", file, 'exec'), glob, loc)
File "/Users/lakshmananp2/PycharmProjects/Scratch/exception.py", line 9, in <module>
raise ex(e)
TypeError: 'ZeroDivisionError' object is not callable
ex is an instance of ZeroDivisionError, not the type ZeroDivisionError itself.
raise type(ex)(e)
You've close, but you're calling the instance instead of the type. We can build a new instance by grabbing the type of the exception using type builtin:
import time
try:
start_time = time.time()
1/0
except Exception as ex:
elapsed_time = (time.time() - start_time)/60
e = "elapsed time(in mins) - {0}".format(elapsed_time)
error_constructor = type(ex)
raise error_constructor(e)
If you want to retain the original traceback you can instead do:
import time
try:
start_time = time.time()
1/0
except Exception as ex:
elapsed_time = (time.time() - start_time)/60
e = "elapsed time(in mins) - {0}".format(elapsed_time)
ex.__init__(e)
raise # re-raises ex with the original line number

Check user input is valid for decimal?

I want to make sure the input will be number. I have tried testing with signs and letters but the shell just throws an error saying "Invalid literal for Decimal". I'm working on a calculator so thought the decimal module would be best suited. Thanks in advance.
This is my code:
import decimal
while True:
userInput = (raw_input("Enter number:"))
try:
userInput = decimal.Decimal(userInput)
break
except ValueError:
print ("Number please")
Using Python 2.7.6
Catch decimal.InvalidOperation
>>> a = 's'
>>> try:
... decimal.Decimal(a)
... except decimal.InvalidOperation:
... print 'fds'
...
fds
Instead of catching a ValueError, catch a decimal.InvalidOperation error. This error is thrown when invalid data is passed to the decimal.Decimal constructor.
The correct way to check if a value is a valid input for Decimal is:
from decimal import Decimal, DecimalException
try:
Decimal(input_value)
except DecimalException:
pass
https://docs.python.org/2/library/decimal.html#decimal.DecimalException
You are catching the wrong Exception. You are catching a ValueError, but the code throws decimal.InvalidOperation for various inputs that are not valid decimal values.
>python test.py
Enter number:10
>python test.py
Enter number:10.2
>python test.py
Enter number:asdf
Traceback (most recent call last):
File "test.py", line 6, in <module>
userInput = decimal.Decimal(userInput)
File "C:\Python27\lib\decimal.py", line 548, in __new__
"Invalid literal for Decimal: %r" % value)
File "C:\Python27\lib\decimal.py", line 3872, in _raise_error
raise error(explanation)
decimal.InvalidOperation: Invalid literal for Decimal: 'asdf'
>python test.py
Enter number:10.23.23
Traceback (most recent call last):
File "test.py", line 6, in <module>
userInput = decimal.Decimal(userInput)
File "C:\Python27\lib\decimal.py", line 548, in __new__
"Invalid literal for Decimal: %r" % value)
File "C:\Python27\lib\decimal.py", line 3872, in _raise_error
raise error(explanation)
decimal.InvalidOperation: Invalid literal for Decimal: '10.23.23'
Change your except line to except decimal.InvalidOperation:

very simple python 3 try except ValueError not tripping

trying to get my try except block to work.
import sys
def main():
try:
test = int("hello")
except ValueError:
print("test")
raise
main()
output is
C:\Python33>python.exe test.py
test
Traceback (most recent call last):
File "test.py", line 10, in <module>
main()
File "test.py", line 5, in main
test = int("hello")
ValueError: invalid literal for int() with base 10: 'hello'
C:\Python33>
would like the except to trip
You are reraising the exception. It's working as designed.
The test is printed right at the top there before the traceback, but you used raise so the exception still is causing a traceback:
>>> def main():
... try:
... test = int("hello")
... except ValueError:
... print("test")
... raise
...
>>> main()
test
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in main
ValueError: invalid literal for int() with base 10: 'hello'
Remove the raise and just the test print remains:
>>> def main():
... try:
... test = int("hello")
... except ValueError:
... print("test")
...
>>> main()
test

"Uncatching" an exception in python

How should I "rethrow" an exception, that is, suppose:
I try something in my code, and unfortunately it fails.
I try some "clever" workaround, which happens to also fail this time
If I throw the exception from the (failing) workaround, it's going to be pretty darn confusing for the user, so I think it may be best to rethrow the original exception (?), with the descriptive traceback it comes with (about the actual problem)...
Note: the motivating example for this is when calling np.log(np.array(['1'], dtype=object)), where it tries a witty workaround and gives an AttributeError (it's "really" a TypeError).
One way I can think of is just to re-call the offending function, but this seems doged (for one thing theoretically the original function may exert some different behaviour the second time it's called):
Okay this is one awful example, but here goes...
def f():
raise Exception("sparrow")
def g():
raise Exception("coconut")
def a():
f()
Suppose I did this:
try:
a()
except:
# attempt witty workaround
g()
---------------------------------------------------------------------------
Exception Traceback (most recent call last)
<ipython-input-4-c76b7509b315> in <module>()
3 except:
4 # attempt witty workaround
----> 5 g()
6
<ipython-input-2-e641f2f9a7dc> in g()
4
5 def g():
----> 6 raise Exception("coconut")
7
8
Exception: coconut
Well, the problem doesn't really lie with the coconut at all, but the sparrow:
try:
a()
except:
# attempt witty workaround
try:
g()
except:
# workaround failed, I want to rethrow the exception from calling a()
a() # ideally don't want to call a() again
---------------------------------------------------------------------------
Exception Traceback (most recent call last)
<ipython-input-4-e641f2f9a7dc> in <module>()
19 except:
20 # workaround failed, I want to rethrow the exception from calling a()
---> 21 a() # ideally don't want to call a() again
<ipython-input-3-e641f2f9a7dc> in a()
8
9 def a():
---> 10 f()
11
12
<ipython-input-1-e641f2f9a7dc> in f()
1 def f():
----> 2 raise Exception("sparrow")
3
4
5 def g():
Exception: sparrow
Is there a standard way to deal with this, or am I thinking about it completely wrong?
If you want to make it appear to the end user that you never called g(), then you need to store the traceback from the first error, call the second function and then throw the original with the original traceback. (otherwise, in Python2, bare raise re-raises the second exception rather than the first). The problem is that there is no 2/3 compatible way to raise with traceback, so you have to wrap the Python 2 version in an exec statement (since it's a SyntaxError in Python 3).
Here's a function that lets you do that (I added this to the pandas codebase recently):
import sys
if sys.version_info[0] >= 3:
def raise_with_traceback(exc, traceback=Ellipsis):
if traceback == Ellipsis:
_, _, traceback = sys.exc_info()
raise exc.with_traceback(traceback)
else:
# this version of raise is a syntax error in Python 3
exec("""
def raise_with_traceback(exc, traceback=Ellipsis):
if traceback == Ellipsis:
_, _, traceback = sys.exc_info()
raise exc, None, traceback
""")
raise_with_traceback.__doc__ = (
"""Raise exception with existing traceback.
If traceback is not passed, uses sys.exc_info() to get traceback."""
)
And then you can use it like this (I also changed the Exception types for clarity).
def f():
raise TypeError("sparrow")
def g():
raise ValueError("coconut")
def a():
f()
try:
a()
except TypeError as e:
import sys
# save the traceback from the original exception
_, _, tb = sys.exc_info()
try:
# attempt witty workaround
g()
except:
raise_with_traceback(e, tb)
And in Python 2, you only see a() and f():
Traceback (most recent call last):
File "test.py", line 40, in <module>
raise_with_traceback(e, tb)
File "test.py", line 31, in <module>
a()
File "test.py", line 28, in a
f()
File "test.py", line 22, in f
raise TypeError("sparrow")
TypeError: sparrow
But in Python 3, it still notes there was an additional exception too, because you are raising within its except clause [which flips the order of the errors and makes it much more confusing for the user]:
Traceback (most recent call last):
File "test.py", line 38, in <module>
g()
File "test.py", line 25, in g
raise ValueError("coconut")
ValueError: coconut
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "test.py", line 40, in <module>
raise_with_traceback(e, tb)
File "test.py", line 6, in raise_with_traceback
raise exc.with_traceback(traceback)
File "test.py", line 31, in <module>
a()
File "test.py", line 28, in a
f()
File "test.py", line 22, in f
raise TypeError("sparrow")
TypeError: sparrow
If you absolutely want it to look like the g() Exception never happened in both Python 2 and Python 3, you need to check that you are out of the except clause first:
try:
a()
except TypeError as e:
import sys
# save the traceback from the original exception
_, _, tb = sys.exc_info()
handled = False
try:
# attempt witty workaround
g()
handled = True
except:
pass
if not handled:
raise_with_traceback(e, tb)
Which gets you the following traceback in Python 2:
Traceback (most recent call last):
File "test.py", line 56, in <module>
raise_with_traceback(e, tb)
File "test.py", line 43, in <module>
a()
File "test.py", line 28, in a
f()
File "test.py", line 22, in f
raise TypeError("sparrow")
TypeError: sparrow
And this traceback in Python 3:
Traceback (most recent call last):
File "test.py", line 56, in <module>
raise_with_traceback(e, tb)
File "test.py", line 6, in raise_with_traceback
raise exc.with_traceback(traceback)
File "test.py", line 43, in <module>
a()
File "test.py", line 28, in a
f()
File "test.py", line 22, in f
raise TypeError("sparrow")
TypeError: sparrow
It does add an additional non-useful line of traceback that shows the raise exc.with_traceback(traceback) to the user, but it is relatively clean.
Here is something totally nutty that I wasn't sure would work, but it works in both python 2 and 3. (It does however, require the exception to be encapsulated into a function...)
def f():
print ("Fail!")
raise Exception("sparrow")
def g():
print ("Workaround fail.")
raise Exception("coconut")
def a():
f()
def tryhard():
ok = False
try:
a()
ok = True
finally:
if not ok:
try:
g()
return # "cancels" sparrow Exception by returning from finally
except:
pass
>>> tryhard()
Fail!
Workaround fail.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in tryhard
File "<stdin>", line 2, in a
File "<stdin>", line 3, in f
Exception: sparrow
Which is the correct exception and the right stack trace, and with no hackery.
>>> def g(): print "Worked around." # workaround is successful in this case
>>> tryhard()
Fail!
Worked around.
>>> def f(): print "Success!" # normal method works
>>> tryhard()
Success!
Ian Bicking has a nice primer on re-raising.
As a corollary, my rule is to only catch Exceptions that the code knows how to deal with. Very few methods actually hit this rule. For example, if I'm reading a file and an IOException is thrown, there is very little that method could reasonably do.
As a corollary to that, catching exceptions in "main" is reasonable if you can return to a good state and you don't just want to dump the user out; this only obtains in interactive programs.
The relevant section from the primer being the update:
try:
a()
except:
exc_info = sys.exc_info()
try:
g()
except:
# If this happens, it clobbers exc_info,
# which is why we had to save it above
import traceback
print >> sys.stderr, "Error in revert_stuff():"
# py3 print("Error in revert_stuff():", file=sys.stderr)
traceback.print_exc()
raise exc_info[0], exc_info[1], exc_info[2]
In python 3, the final raise could be written as:
ei = exc_info[1]
ei.filname = exc_info[0]
ei.__traceback__ = exc_info[2]
raise ei
In Python 3 (specifically tested on 3.3.2), this all works better, there's no need for saving sys.exc_info. Don't re-raise the original exception within the second exception handler. Just note that the 2nd attempt failed and raise the original in the scope of the original handler, like so:
#!python3
try:
a()
except Exception:
g_failed = False
try:
g()
except Exception:
g_failed = True
raise
Python 3 output correctly raising "sparrow" and showing traceback through a() and f():
Traceback (most recent call last):
File "x3.py", line 13, in <module>
a()
File "x3.py", line 10, in a
f()
File "x3.py", line 4, in f
raise Exception("sparrow")
Exception: sparrow
However, the same script on Python 2 incorrectly raising "coconut" and only showing g():
Traceback (most recent call last):
File "x3.py", line 17, in <module>
g()
File "x3.py", line 7, in g
raise Exception("coconut")
Exception: coconut
Here are the modifications to make Python 2 work correctly:
#!python2
import sys
try:
a()
except Exception:
exc = sys.exc_info()
try:
g()
except Exception:
raise exc[0], exc[1], exc[2] # Note doesn't care that it is nested.
Now Python 2 correctly shows "sparrow" and both a() and f() traceback:
Traceback (most recent call last):
File "x2.py", line 14, in <module>
a()
File "x2.py", line 11, in a
f()
File "x2.py", line 5, in f
raise Exception("sparrow")
Exception: sparrow
Capture the error in your except clause, then manually re-raise it later. Capture the traceback, and reprint it via the traceback module.
import sys
import traceback
def f():
raise Exception("sparrow")
def g():
raise Exception("coconut")
def a():
f()
try:
print "trying a"
a()
except Exception as e:
print sys.exc_info()
(_,_,tb) = sys.exc_info()
print "trying g"
try:
g()
except:
print "\n".join(traceback.format_tb(tb))
raise e
In Python 3, within a function, this can be done in a very slick way, following up the answer from #Mark Tolonen, who uses a boolean. You can't do this outside a function because there's no way to break out of the outer try statement: the function is needed for return.
#!python3
def f():
raise Exception("sparrow")
def g():
raise Exception("coconut")
def a():
f()
def h():
try:
a()
except:
try:
g()
return # Workaround succeeded!
except:
pass # Oh well, that didn't work.
raise # Re-raises *first* exception.
h()
This results in:
Traceback (most recent call last):
File "uc.py", line 23, in <module>
h()
File "uc.py", line 14, in h
a()
File "uc.py", line 10, in a
f()
File "uc.py", line 4, in f
raise Exception("sparrow")
Exception: sparrow
...and if instead g succeeds:
def g(): pass
...then it doesn't raise an exception.
try:
1/0 # will raise ZeroDivisionError
except Exception as first:
try:
x/1 # will raise NameError
except Exception as second:
raise first # will re-raise ZeroDivisionError

Categories