Concealing an exception root cause
In Python 3, exceptions contain a root cause. The default behavior of internally raised exceptions is to use an implicit __context__
to include the root cause of an exception. In some cases, we may want to deemphasize the root cause because it's misleading or unhelpful for debugging.
This technique is almost always paired with an application or library that defines a unique exception. The idea is to show the unique exception without the clutter of an irrelevant exception from outside the application or library.
Getting ready
Assume we're writing some complex string processing. We'd like to treat a number of different kinds of detailed exceptions as a single generic error so that users of our software are insulated from the implementation details. We can attach details to the generic error.
How to do it...
- To create a new exception, we can do this:
>>> class MyAppError(Exception): ... pass
This creates a new, unique class of exception that our library or application can use.
- When handling exceptions, we can conceal the root cause exception like this:
>>> try: ... None.some_method(42) ... except AttributeError as exception: ... raise MyAppError("Some Known Problem") from None
In this example, we raise a new exception instance of the module's unique MyAppError
exception class. The new exception will not have any connection with the root cause AttributeError
exception.
How it works...
The Python exception classes all have a place to record the cause of the exception. We can set this __cause__
attribute using the raise Visible from RootCause
statement. This is done implicitly using the exception context as a default if the from
clause is omitted.
Here's how it looks when this exception is raised:
>>> try:
... None.some_method(42)
... except AttributeError as exception:
... raise MyAppError("Some Known Problem") from None
Traceback (most recent call last):
File "/Applications/PyCharm CE.app/Contents/helpers/pycharm/docrunner.py", line 139, in __run
exec(compile(example.source, filename, "single",
File "<doctest examples.txt[67]>", line 4, in <module>
raise MyAppError("Some Known Problem") from None
MyAppError: Some Known Problem
The underlying cause has been concealed. If we omit from None
, then the exception will include two parts and will be quite a bit more complex. When the root cause is shown, the output looks like this:
Traceback (most recent call last):
File "<doctest examples.txt[66]>", line 2, in <module>
None.some_method(42)
AttributeError: 'NoneType' object has no attribute 'some_method'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Applications/PyCharm CE.app/Contents/helpers/pycharm/docrunner.py", line 139, in __run
exec(compile(example.source, filename, "single",
File "<doctest examples.txt[66]>", line 4, in <module>
raise MyAppError("Some Known Problem")
MyAppError: Some Known Problem
This shows the underlying AttributeError
. This may be an implementation detail that's unhelpful and better left off the printed display of the exception.
There's more...
There are a number of internal attributes of an exception. These include __cause__
, __context__
, __traceback__
, and __suppress_context__
. The overall exception context is in the __context__
attribute. The cause, if provided via a raise from
statement, is in __cause__
. The context for the exception is available but can be suppressed from being printed.
See also
- In the Leveraging the exception matching rules recipe, we look at some considerations when designing exception-handling statements.
- In the Avoiding a potential problem with an except: clause recipe, we look at some additional considerations when designing exception-handling statements.