48

What happens to my first exception (A) when the second (B) is raised in the following code?

class A(Exception): pass class B(Exception): pass try: try: raise A('first') finally: raise B('second') except X as c: print(c) 

If run with X = A I get:

Traceback (most recent call last): File "raising_more_exceptions.py", line 6, in raise A('first') __main__.A: first During handling of the above exception, another exception occurred: Traceback (most recent call last): File "raising_more_exceptions.py", line 8, in raise B('second') __main__.B: second

But if X = B I get:

second 

I'm wondering:

  1. Where did my first exception go?
  2. Why is only the outermost exception catchable?
  3. How do I peel off the outermost exception and reraise the earlier exceptions?

This question specifically addresses Python 3, as its exception handling is quite different to Python 2.

1
  • The answers seem to be neglecting the fact that I'm still getting a full traceback when the exception is not caught. Please explain? Commented Jun 8, 2011 at 14:46

5 Answers 5

34

Answering to question 3, you can use:

raise B('second') from None 

Which will remove the exception A traceback.

Traceback (most recent call last): File "raising_more_exceptions.py", line 8, in raise B('second') __main__.B: second 
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks, this solved my issue... wanted to raise a custom exception from a KeyError in a base class method. My List is a sub class attribute and sub class objects are dynamically generated. The default KeyError trackback didn't say which sub class the error occurred in, so I wanted to suppress it and raise a new exception with more info. "Raise X from None" allows me to do this.
If you want to save original traceback you can use: raise OtherException(...).with_traceback(tb) as described in docs docs.python.org/3/library/exceptions.html#BaseException
This isn't what question 3 asked. Question 3 asked how to remove the B and reraise the A, not how to raise just the B and suppress the A.
16

The 'causing' exception is available as c.__context__ in your last exception handler. Consider your second example where X = B (replaced X with B in the example below):

try: try: raise A('first') finally: raise B('second') except B as c: print(repr(c)) print(repr(c.__context__)) 

This would output:

B('second') A('first') 

Note that c.__context__ points to A('first'), which is where the first exception went.

Python is using this information to render a more useful traceback. Under Python 2.x the original exception would have been lost, this is for Python 3 only.

Typically you would use this to throw a consistent exception while still keeping the original exception accessible (although it's pretty cool that it happens automatically from an exception handler, I didn't know that!):

try: do_something_involving_http() except (URLError, socket.timeout) as ex: raise MyError('Network error') from ex 

More info (and some other pretty useful things you can do) here: http://docs.python.org/3.3/library/exceptions.html

Comments

9

Pythons exception handling will only deal with one exception at a time. However, exception objects are subject to the same variable rules and garbage collection as everything else. Hence, if you save the exception object in a variable somewhere you can deal with it later, even if another exception is raised.

In your case, when an exception is raised during the "finally" statement, Python 3 will print out the traceback of the first exception before the one of the second exception, to be more helpful.

A more common case is that you want to raise an exception during an explicit exception handling. Then you can "save" the exception in the next exception. Just pass it in as a parameter:

>>> class A(Exception): ... pass ... >>> class B(Exception): ... pass ... >>> try: ... try: ... raise A('first') ... except A as e: ... raise B('second', e) ... except Exception as c: ... print(c.args[1]) ... first 

As you see you can now access the original exception.

4 Comments

@Matt: It doesn't go anywhere. I do realize I had a brainfart and updated my answer, though.
How does one handle this situation in doctests?
Or can use raise x from y?
If you only support Python 3, yes. Which is reasonable now, but not in 2011.
7

I believe all the ingredients to answer your question(s) are already in the existing answers. Let me combine and elaborate.

Let me repeat your question's code to provide line number references:

 1 class A(Exception): pass 2 class B(Exception): pass 3 4 try: 5 try: 6 raise A('first') 7 finally: 8 raise B('second') 9 except X as c: 10 print(c) 

So to answer your questions:

  1. Where did my first exception go?

Your first exception A is raised in line 6. The finally clause in line 7 is always executed as soon as the try block (lines 5-6) is left, regardless if it is left because of successful completion or because of a raised exception. While the finally clause is being executed, line 8 raises another exception B. As Lennart and Ignazio have pointed out, only one exception, the one that is most recently being raised, can be kept track of. So as soon as B is raised, the overall try block (lines 4-8) is quit and the exception B is being caught by the except statement in line 9 if it matches (if X is B).

  1. Why is only the outermost exception catchable?

Hopefully this is clear now from my explanation of 1. You could catch the inner/lower/first exception, though. To merge in Lennart's answer, slightly modified, here's how to catch both:

class A(Exception): pass class B(Exception): pass try: try: raise A('first') except A as e: raise B('second', e) except Exception as c: print(c) 

The output is:

('second', A('first',)) 
  1. How do I peel off the outermost exception and reraise the earlier exceptions?

In Lennart's example the solution to this question is the line except A as e where the inner/lower/first exception is being caught and stored in variable e.

As a general gut-feeling of when to catch exceptions, when to ignore them, and when to re-raise, maybe this question and Alex Martelli's answer help.

Comments

6
  1. It got thrown out.
  2. Only one exception can be "active" at a time per thread.
  3. You can't, unless you encapsulate the earlier exception in the later exception somehow.

3 Comments

If it got thrown out, why am I getting a full traceback when the exception is not caught?
Because the tool that you're using to run the script has installed a global exception handler, and it has noticed the double exception.
I see now, Python 3.x has different behavior. It is the interpreter itself catching it. In 2.x the first exception is silently dropped.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.