199

I know that if I want to re-raise an exception, I simple use raise without arguments in the respective except block. But given a nested expression like

try:
    something()
except SomeError as e:
    try:
        plan_B()
    except AlsoFailsError:
        raise e  # I'd like to raise the SomeError as if plan_B()
                 # didn't raise the AlsoFailsError

how can I re-raise the SomeError without breaking the stack trace? raise alone would in this case re-raise the more recent AlsoFailsError. Or how could I refactor my code to avoid this issue?

7
  • 3
    Have you tried putting plan_B in another function that returns True on success, and False on exception? Then the outer except block could just be if not try_plan_B(): raise Commented Aug 12, 2013 at 13:46
  • @DrewMcGowen Unfortunately the more realistic case is that this is inside a function accepting arbitrary objects arg and I'd try calling arg.plan_B() which might raise an AttributeError due to arg not providing a plan B Commented Aug 12, 2013 at 13:47
  • Have a look at the traceback module: docs.python.org/2/library/traceback.html#traceback-examples
    – Paco
    Commented Aug 12, 2013 at 13:51
  • @Paco Thanks, I will (Though an answer already shows a simpler way) Commented Aug 12, 2013 at 13:52
  • @DrewMcGowen I wrote up an answer based on your comment, which looks less pythonic than user4815162342's answer though. But that's due to my wanting to also have a return value and allowing plan_B to raise exceptions Commented Aug 12, 2013 at 14:11

4 Answers 4

231

As of Python 3, the traceback is stored in the exception, so a simple raise e will do the (mostly) right thing:

try:
    something()
except SomeError as e:
    try:
        plan_B()
    except AlsoFailsError:
        raise e  # or raise e from None - see below

The traceback produced will include an additional notice that SomeError occurred while handling AlsoFailsError (because of raise e being inside except AlsoFailsError). This is misleading because what actually happened is the other way around - we encountered AlsoFailsError, and handled it, while trying to recover from SomeError. To obtain a traceback that doesn't include AlsoFailsError, replace raise e with raise e from None.


In Python 2 you'd store the exception type, value, and traceback in local variables and use the three-argument form of raise:

try:
    something()
except SomeError:
    t, v, tb = sys.exc_info()
    try:
        plan_B()
    except AlsoFailsError:
        raise t, v, tb
15
  • Perfect, that's what I just also found here, thanks! Though there the suggestion is raise self.exc_info[1], None, self.exc_info[2] after self.exc_info = sys.exc_info() - putting [1] to first position for some reason Commented Aug 12, 2013 at 13:48
  • 3
    @TobiasKienzler raise t, None, tb will lose the value of the exception and will force raise to re-instantiate it from the type, giving you a less specific (or simply incorrect) exception value. For example, if the raised exception is KeyError("some-key"), it will just re-raise KeyError() and omit the exact missing key from the traceback. Commented Aug 12, 2013 at 13:53
  • 3
    @TobiasKienzler It should still possible to express that in Python 3 as raise v.with_traceback(tb). (Your comment even says as much, except it proposes to re-instantiate the value.) Commented Aug 12, 2013 at 14:42
  • 2
    Also, the red warning not to store sys.exc_info() in a local variable made sense prior to Python 2.0 (released 13 years ago), but borders on ridiculous today. Modern Python would be near-useless without the cycle collector, as every non-trivial Python library creates cycles without pause and depends on their correct cleanup. Commented Aug 12, 2013 at 14:44
  • 2
    @user4815162342 You can kill the "another error occurred" nested error by writing "raise e from None". Commented Apr 26, 2017 at 15:26
30

Even if the accepted solution is right, it's good to point to the Six library which has a Python 2+3 solution, using six.reraise.

six.reraise(exc_type, exc_value, exc_traceback=None)

Reraise an exception, possibly with a different traceback. [...]

So, you can write:

import six


try:
    something()
except SomeError:
    t, v, tb = sys.exc_info()
    try:
        plan_B()
    except AlsoFailsError:
        six.reraise(t, v, tb)
3
  • 1
    Good point - speaking of Six you can also use six.raise_from if you want to include information that plan_B() also failed. Commented Sep 28, 2017 at 10:31
  • 1
    @TobiasKienzler: I think it's a different usage: with six.raise_from you create a new exception which is linked to a previous one, you don't re-raise, so the trace back is different. Commented Sep 28, 2017 at 10:40
  • 1
    My point exactly - if you reraise you get the impression only something() threw SomeError, if you raise_from you also know that this caused plan_B() to be executed but throwing the AlsoFailsError. So it depends on the usecase. I think raise_from will make debugging easier Commented Sep 28, 2017 at 10:45
21

As per Drew McGowen's suggestion, but taking care of a general case (where a return value s is present), here's an alternative to user4815162342's answer:

try:
    s = something()
except SomeError as e:
    def wrapped_plan_B():
        try:
            return False, plan_B()
        except:
            return True, None
    failed, s = wrapped_plan_B()
    if failed:
        raise
5
  • 1
    The nice thing about this approach is that it works unchanged in Python 2 and 3. Commented Feb 3, 2017 at 18:21
  • 3
    @user4815162342 Good point :) Though meanwhile in Python3 I'd consider raise from, so the stack trace would also let me se plan B failed. Which can be emulated in Python 2 by the way. Commented Feb 4, 2017 at 21:30
  • you pass some error as 'e' but then dont use it? Also this doesn't reraise the the specific exception. Commented Nov 9, 2022 at 21:08
  • 1
    @openCivilisation as e could be omitted, it's just a snippet that could be used in a more general way. But what do you mean be "specific exception"? raise alone re-raises the exception from something() currently handled in the except scope, which was the intention Commented Nov 27, 2022 at 8:01
  • 1
    FYI, a bare raisere-raises the exception that is currently being handled” by try-except. Commented Aug 14, 2023 at 7:50
10

Python 3.5+ attaches the traceback information to the error anyway, so it's no longer necessary to save it separately.

>>> def f():
...   try:
...     raise SyntaxError
...   except Exception as e:
...     err = e
...     try:
...       raise AttributeError
...     except Exception as e1:
...       raise err from None
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 9, in f
  File "<stdin>", line 3, in f
SyntaxError: None
>>> 
4
  • 2
    The question is about another exception happening during the except. But you're right, when I replace err = e by, say, raise AttributeError, you get first the SyntaxError stack trace, followed by a During handling of the above exception, another exception occurred: and the AttributeError stack trace. Good to know, though unfortunately one cannot rely on 3.5+ being installed. PS: ff verstehen nicht-Deutsche vermutlich nicht ;) Commented Apr 26, 2017 at 5:45
  • OK, so I changed the example to raise another exception, which (as the original question asked for) gets ignored when I re-raise the first one. Commented Apr 26, 2017 at 9:06
  • 3
    @TobiasKienzler 3.5+ (which I changed it to) seems to be a globally recognized format. Was denkst du? ;)
    – linusg
    Commented Apr 5, 2018 at 22:33
  • @linusg Agreed :) Commented Apr 6, 2018 at 9:45

Not the answer you're looking for? Browse other questions tagged or ask your own question.