5

In my code I want to catch an exception when it occurs, print some information abut the exception to the screen, and then end the script once I have done so. I tried to use something equivalent to the following code, but I don't understand why I get the traceback I do.

When executing:

try:
    1 / 0
except ZeroDivisionError:
    print("Exception: ZeroDivisionError")
    raise Exception

Console reads:

Exception: ZeroDivisionError
Traceback (most recent call last):
  File "<pyshell#19>", line 2, in <module>
    1 / 0
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<pyshell#19>", line 5, in <module>
    raise Exception
Exception

I thought that if I catch the ZeroDivisionError, it would no longer be raised, and the only thing that would show is the raise Exception I do at the end, but both show in the console.

Why do they both show, and how do I alter the code so only the second shows, or is there a better way to achieve what I want?

user3873725
  • 67
  • 1
  • 4

1 Answers1

6

The console shows the context here; when an exception is raised from an exception handler, Python attaches the active exception as the __context__ attribute and Python shows that context later on if the new exception is not being handled. If you don't want the context to be shown, you need to supply a cause instead; you can supply an empty cause with with raise ... from None:

try:
    1 / 0
except ZeroDivisionError:
    print("Exception: ZeroDivisionError")
    raise Exception from None

Quoting the raise statement documentation:

The from clause is used for exception chaining: if given, the second expression must be another exception class or instance, which will then be attached to the raised exception as the __cause__ attribute (which is writable). If the raised exception is not handled, both exceptions will be printed[...]

A similar mechanism works implicitly if an exception is raised inside an exception handler: the previous exception is then attached as the new exception’s __context__ attribute[...]

And from the Exceptions documentation:

When raising (or re-raising) an exception in an except clause __context__ is automatically set to the last exception caught; if the new exception is not handled the traceback that is eventually displayed will include the originating exception(s) and the final exception.

When raising a new exception (rather than using a bare raise to re-raise the exception currently being handled), the implicit exception context can be supplemented with an explicit cause by using from with raise:

raise new_exc from original_exc

The expression following from must be an exception or None. It will be set as __cause__ on the raised exception. Setting __cause__ also implicitly sets the __suppress_context__ attribute to True, so that using raise new_exc from None effectively replaces the old exception with the new one for display purposes (e.g. converting KeyError to AttributeError), while leaving the old exception available in __context__ for introspection when debugging.

The default traceback display code shows these chained exceptions in addition to the traceback for the exception itself. An explicitly chained exception in __cause__ is always shown when present. An implicitly chained exception in __context__ is shown only if __cause__ is None and __suppress_context__ is false.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343