PEP 409 – Suppressing exception context
- Author:
- Ethan Furman <ethan at stoneleaf.us>
- Status:
- Final
- Type:
- Standards Track
- Created:
- 26-Jan-2012
- Python-Version:
- 3.3
- Post-History:
- 30-Aug-2002, 01-Feb-2012, 03-Feb-2012
- Superseded-By:
- 415
- Resolution:
- Python-Dev message
Table of Contents
Abstract
One of the open issues from PEP 3134 is suppressing context: currently there is no way to do it. This PEP proposes one.
Rationale
There are two basic ways to generate exceptions:
- Python does it (buggy code, missing resources, ending loops, etc.)
- manually (with a raise statement)
When writing libraries, or even just custom classes, it can become necessary to raise exceptions; moreover it can be useful, even necessary, to change from one exception to another. To take an example from my dbf module:
try:
value = int(value)
except Exception:
raise DbfError(...)
Whatever the original exception was (ValueError
, TypeError
, or
something else) is irrelevant. The exception from this point on is a
DbfError
, and the original exception is of no value. However, if
this exception is printed, we would currently see both.
Alternatives
Several possibilities have been put forth:
raise as NewException()
Reuses the
as
keyword; can be confusing since we are not really reraising the originating exceptionraise NewException() from None
Follows existing syntax of explicitly declaring the originating exception
exc = NewException(); exc.__context__ = None; raise exc
Very verbose way of the previous method
raise NewException.no_context(...)
Make context suppression a class method.
All of the above options will require changes to the core.
Proposal
I propose going with the second option:
raise NewException from None
It has the advantage of using the existing pattern of explicitly setting the cause:
raise KeyError() from NameError()
but because the cause is None
the previous context is not displayed
by the default exception printing routines.
Implementation Discussion
Note: after acceptance of this PEP, a cleaner implementation mechanism was proposed and accepted in PEP 415. Refer to that PEP for more details on the implementation actually used in Python 3.3.
Currently, None
is the default for both __context__
and __cause__
.
In order to support raise ... from None
(which would set __cause__
to
None
) we need a different default value for __cause__
. Several ideas
were put forth on how to implement this at the language level:
- Overwrite the previous exception information (side-stepping the issue and
leaving
__cause__
atNone
).Rejected as this can seriously hinder debugging due to poor error messages.
- Use one of the boolean values in
__cause__
:False
would be the default value, and would be replaced whenfrom ...
was used with the explicitly chained exception orNone
.Rejected as this encourages the use of two different objects types for
__cause__
with one of them (boolean) not allowed to have the full range of possible values (True
would never be used). - Create a special exception class,
__NoException__
.Rejected as possibly confusing, possibly being mistakenly raised by users, and not being a truly unique value as
None
,True
, andFalse
are. - Use
Ellipsis
as the default value (the...
singleton).Accepted.
Ellipses are commonly used in English as place holders when words are omitted. This works in our favor here as a signal that
__cause__
is omitted, so look in__context__
for more details.Ellipsis is not an exception, so cannot be raised.
There is only one Ellipsis, so no unused values.
Error information is not thrown away, so custom code can trace the entire exception chain even if the default code does not.
Language Details
To support raise Exception from None
, __context__
will stay as it is,
but __cause__
will start out as Ellipsis
and will change to None
when the raise Exception from None
method is used.
form | __context__ | __cause__ |
---|---|---|
raise | None |
Ellipsis |
reraise | previous exception | Ellipsis |
reraise from None | ChainedException |
previous exception | None | explicitly chained exception |
The default exception printing routine will then:
- If
__cause__
isEllipsis
the__context__
(if any) will be printed. - If
__cause__
isNone
the__context__
will not be printed. - if
__cause__
is anything else,__cause__
will be printed.
In both of the latter cases the exception chain will stop being followed.
Because the default value for __cause__
is now Ellipsis
and raise
Exception from Cause
is simply syntactic sugar for:
_exc = NewException()
_exc.__cause__ = Cause()
raise _exc
Ellipsis
, as well as None
, is now allowed as a cause:
raise Exception from Ellipsis
Patches
There is a patch for CPython implementing this attached to Issue 6210.
References
Discussion and refinements in this thread on python-dev.
Copyright
This document has been placed in the public domain.
Source: https://github.com/python/peps/blob/main/pep-0409.txt
Last modified: 2022-08-24 22:40:18 GMT