177

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?

Tobias Kienzler
  • 25,759
  • 22
  • 127
  • 221
  • 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` – Drew McGowen Aug 12 '13 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 – Tobias Kienzler Aug 12 '13 at 13:47
  • Have a look at the traceback module: http://docs.python.org/2/library/traceback.html#traceback-examples – Paco Aug 12 '13 at 13:51
  • @Paco Thanks, I will (Though [an answer](http://stackoverflow.com/a/18188660/321973) already shows a simpler way) – Tobias Kienzler Aug 12 '13 at 13:52
  • @DrewMcGowen I wrote up [an answer based on your comment](http://stackoverflow.com/a/18189174/321973), which looks less pythonic than [user4815162342's answer](http://stackoverflow.com/a/18188660/321973) though. But that's due to my wanting to also have a return value and allowing `plan_B` to raise exceptions – Tobias Kienzler Aug 12 '13 at 14:11
  • I'm confused by the code example. There is no `raise` w/o arguments. The code correcly reraises 1st exception. The comment is misleading: why reraise is in `except AlsoFailsError` block if the comment tells "as if plan_B() **didn't** raise the AlsoFailsError". – WloHu Oct 16 '18 at 08:48
  • @JCode Honestly I don't remember my exact thought from five years ago, the example looks exactly like the accepted answer's Python3 code... But as [mentioned below](https://stackoverflow.com/questions/18188563/how-to-re-raise-an-exception-in-nested-try-except-blocks?noredirect=1#comment71264711_18189174) I'd probably use `raise from` now anyway – Tobias Kienzler Oct 16 '18 at 14:02

4 Answers4

216

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
Mateen Ulhaq
  • 24,552
  • 19
  • 101
  • 135
user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • Perfect, that's what I just also found [here](http://nedbatchelder.com/blog/200711/rethrowing_exceptions_in_python.html), 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 – Tobias Kienzler Aug 12 '13 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. – user4815162342 Aug 12 '13 at 13:53
  • So your solution is better. In fact that other post seems to call `raise v, None, tb`, which should probably fail due to the exception's class missing. Hm, [sys.exc_info()](http://docs.python.org/2.7/library/sys.html#sys.exc_info) has a pretty red warning about assigning the traceback value to a local variable, does this require being taken care of in this case? **edit** Ah, there's a box below stating that starting from 2.2 that's handled automatically though it should still be avoided when possible – Tobias Kienzler Aug 12 '13 at 13:56
  • I hacked up [another solution](http://stackoverflow.com/a/18189174/321973) that doesn't require `sys`, but it looks less pythonic to me, so I accept your answer nonetheless – Tobias Kienzler Aug 12 '13 at 14:14
  • Ah, note that as stated [here](http://stackoverflow.com/questions/8760267/re-raise-python-exception-and-preserve-stack-trace#comment24566185_8760670), this won't work in Python 3 due to a different [raise syntax](http://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) – Tobias Kienzler Aug 12 '13 at 14:25
  • 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.) – user4815162342 Aug 12 '13 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. – user4815162342 Aug 12 '13 at 14:44
  • Thanks for clarifying, then that warning shouldn't be so prominent in 2.7's docs... Re the Python 3 issue, I misread the `sys.exc_info()` doc thinking the second return value were not the exception instance but the parameters passed to its `__init__` [m-/](http://picardfacepalm.com/) – Tobias Kienzler Aug 12 '13 at 14:55
  • get a `TypeError: exceptions must be old-style classes or derived from BaseException, not NoneType` for ` raise t, v, tb` – HackToHell May 29 '16 at 06:50
  • @HackToHell Does that happen every time, or only occasionally? Can you print `sys.exc_info()`? Is there additional code between `except SomeError` and `t, v, tb = sys.exc_info()` lines? – user4815162342 May 30 '16 at 06:34
  • @user4815162342 I found your comment WRT Python 3 (`raise v.with_traceback(tb)`) very useful. Would you consider adding it to your answer? – emorris Feb 02 '17 at 16:05
  • With Python3.5 the whole dance is not necessary anyway because the traceback is already stored in the exception. Thus `except SomeError as v:` and `raise v` is sufficient. – Matthias Urlichs Apr 26 '17 at 09:07
  • @MatthiasUrlichs You're right, I've now removed `.with_traceback(tb)` and the call to `sys.exc_info()`. Unfortunately Python 3 specifies *both* tracebacks with the "during handling of the above exception, another exception occurred" message, which is likely not wanted here. The amended answer includes the workaround for that as well. – user4815162342 Apr 26 '17 at 11:13
  • 2
    @user4815162342 You can kill the "another error occurred" nested error by writing "raise e from None". – Matthias Urlichs Apr 26 '17 at 15:26
  • `t, v, tb = sys.exc_info() raise t(v).with_traceback(tb) ` it works for python2/3 both – kkkobelief24 Feb 07 '21 at 06:18
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)
Laurent LAPORTE
  • 21,958
  • 6
  • 58
  • 103
  • 1
    Good point - speaking of Six you can also use [`six.raise_from`](https://pythonhosted.org/six/#six.raise_from) if you want to include information that `plan_B()` also failed. – Tobias Kienzler Sep 28 '17 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. – Laurent LAPORTE Sep 28 '17 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 – Tobias Kienzler Sep 28 '17 at 10:45
19

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
Community
  • 1
  • 1
Tobias Kienzler
  • 25,759
  • 22
  • 127
  • 221
  • 1
    The nice thing about this approach is that it works unchanged in Python 2 and 3. – user4815162342 Feb 03 '17 at 18:21
  • 2
    @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](http://stackoverflow.com/a/29833994/321973) by the way. – Tobias Kienzler Feb 04 '17 at 21:30
  • you pass some error as 'e' but then dont use it? Also this doesn't reraise the the specific exception. – openCivilisation Nov 09 '22 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 – Tobias Kienzler Nov 27 '22 at 08:01
  • FYI, a bare `raise` “[re-raises the exception that is currently being handled](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement)” by try-except. – SnzFor16Min Aug 14 '23 at 07:50
9

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
>>> 
linusg
  • 6,289
  • 4
  • 28
  • 78
Matthias Urlichs
  • 2,301
  • 19
  • 29
  • 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 ;) – Tobias Kienzler Apr 26 '17 at 05: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. – Matthias Urlichs Apr 26 '17 at 09:06
  • 3
    @TobiasKienzler 3.5+ (which I changed it to) seems to be a globally recognized format. Was denkst du? ;) – linusg Apr 05 '18 at 22:33
  • @linusg Agreed :) – Tobias Kienzler Apr 06 '18 at 09:45