PEP 678: convert footnotes to inline links and hard wrap to 79 cols (#2314)
This commit is contained in:
parent
389e8bd43e
commit
f2c28db177
246
pep-0678.rst
246
pep-0678.rst
|
@ -15,38 +15,41 @@ Post-History: 2022-01-27
|
||||||
Abstract
|
Abstract
|
||||||
========
|
========
|
||||||
Exception objects are typically initialized with a message that describes the
|
Exception objects are typically initialized with a message that describes the
|
||||||
error which has occurred. Because further information may be available when the
|
error which has occurred. Because further information may be available when
|
||||||
exception is caught and re-raised, or included in an ``ExceptionGroup``, this PEP
|
the exception is caught and re-raised, or included in an ``ExceptionGroup``,
|
||||||
proposes to add a ``.__note__`` attribute and update the builtin traceback formatting
|
this PEP proposes to add a ``.__note__`` attribute and update the builtin
|
||||||
code to include it in the formatted traceback following the exception string.
|
traceback formatting code to include it in the formatted traceback following
|
||||||
|
the exception string.
|
||||||
|
|
||||||
This is particularly useful in relation to :pep:`654` ``ExceptionGroup``\ s, which
|
This is particularly useful in relation to :pep:`654` ``ExceptionGroup``\ s,
|
||||||
make previous workarounds ineffective or confusing. Use cases have been identified
|
which make previous workarounds ineffective or confusing. Use cases have been
|
||||||
in the standard library, Hypothesis and ``cattrs`` packages, and common code
|
identified in the standard library, Hypothesis and ``cattrs`` packages, and
|
||||||
patterns with retries.
|
common code patterns with retries.
|
||||||
|
|
||||||
|
|
||||||
Motivation
|
Motivation
|
||||||
==========
|
==========
|
||||||
When an exception is created in order to be raised, it is usually initialized
|
When an exception is created in order to be raised, it is usually initialized
|
||||||
with information that describes the error that has occurred. There are cases
|
with information that describes the error that has occurred. There are cases
|
||||||
where it is useful to add information after the exception was caught.
|
where it is useful to add information after the exception was caught. For
|
||||||
For example,
|
example,
|
||||||
|
|
||||||
- testing libraries may wish to show the values involved in a failing assertion,
|
- testing libraries may wish to show the values involved in a failing
|
||||||
or the steps to reproduce a failure (e.g. ``pytest`` and ``hypothesis``; example below).
|
assertion, or the steps to reproduce a failure (e.g. ``pytest`` and
|
||||||
- code which retries an operation on error may wish to associate an iteration, timestamp,
|
``hypothesis``; example below).
|
||||||
or other explanation with each of several errors - especially if re-raising them in
|
- code which retries an operation on error may wish to associate an iteration,
|
||||||
an ``ExceptionGroup``.
|
timestamp, or other explanation with each of several errors - especially if
|
||||||
|
re-raising them in an ``ExceptionGroup``.
|
||||||
- programming environments for novices can provide more detailed descriptions
|
- programming environments for novices can provide more detailed descriptions
|
||||||
of various errors, and tips for resolving them (e.g. ``friendly-traceback``).
|
of various errors, and tips for resolving them (e.g. ``friendly-traceback``).
|
||||||
|
|
||||||
Existing approaches must pass this additional information around while keeping
|
Existing approaches must pass this additional information around while keeping
|
||||||
it in sync with the state of raised, and potentially caught or chained, exceptions.
|
it in sync with the state of raised, and potentially caught or chained,
|
||||||
This is already error-prone, and made more difficult by :pep:`654` ``ExceptionGroup``\ s,
|
exceptions. This is already error-prone, and made more difficult by :pep:`654`
|
||||||
so the time is right for a built-in solution. We therefore propose to add a mutable
|
``ExceptionGroup``\ s, so the time is right for a built-in solution. We
|
||||||
field ``__note__`` to ``BaseException``, which can be assigned a string - and
|
therefore propose to add a mutable field ``__note__`` to ``BaseException``,
|
||||||
if assigned, is automatically displayed in formatted tracebacks.
|
which can be assigned a string - and if assigned, is automatically displayed in
|
||||||
|
formatted tracebacks.
|
||||||
|
|
||||||
|
|
||||||
Example usage
|
Example usage
|
||||||
|
@ -64,11 +67,11 @@ Example usage
|
||||||
Add some information
|
Add some information
|
||||||
>>>
|
>>>
|
||||||
|
|
||||||
When collecting exceptions into an exception group, we may want
|
When collecting exceptions into an exception group, we may want to add context
|
||||||
to add context information for the individual errors. In the following
|
information for the individual errors. In the following example with
|
||||||
example with `Hypothesis' proposed support for ExceptionGroup
|
`Hypothesis' proposed support for ExceptionGroup
|
||||||
<https://github.com/HypothesisWorks/hypothesis/pull/3191>`__, each
|
<https://github.com/HypothesisWorks/hypothesis/pull/3191>`__, each exception
|
||||||
exception includes a note of the minimal failing example::
|
includes a note of the minimal failing example::
|
||||||
|
|
||||||
from hypothesis import given, strategies as st, target
|
from hypothesis import given, strategies as st, target
|
||||||
|
|
||||||
|
@ -111,44 +114,50 @@ exception includes a note of the minimal failing example::
|
||||||
|
|
||||||
Non-goals
|
Non-goals
|
||||||
---------
|
---------
|
||||||
``__note__`` is *not* intended to carry structured data. If your note is for use by
|
``__note__`` is *not* intended to carry structured data. If your note is for
|
||||||
a program rather than display to a human, we recommend instead (or additionally) choosing a convention
|
use by a program rather than display to a human, `we recommend
|
||||||
for an attribute like e.g. ``err._parse_errors = ...`` on the error or ``ExceptionGroup`` [1]_ [2]_
|
<https://discuss.python.org/t/accepting-pep-654-exception-groups-and-except/10813/26>`__
|
||||||
|
instead (or additionally) choosing a convention for an attribute, e.g.
|
||||||
|
``err._parse_errors = ...`` on the error or ``ExceptionGroup``.
|
||||||
|
|
||||||
As a rule of thumb, prefer `exception chaining <https://docs.python.org/3/tutorial/errors.html#exception-chaining>`__
|
As a rule of thumb, prefer `exception chaining
|
||||||
when the error is going to be re-raised or handled as an individual error, and prefer
|
<https://docs.python.org/3/tutorial/errors.html#exception-chaining>`__ when the
|
||||||
``__note__`` when you are collecting multiple exception objects to handle together or later. [3]_
|
error is going to be re-raised or handled as an individual error, and prefer
|
||||||
|
``__note__`` when you are collecting multiple exception objects to handle
|
||||||
|
together or later. [1]_
|
||||||
|
|
||||||
|
|
||||||
Specification
|
Specification
|
||||||
=============
|
=============
|
||||||
|
|
||||||
``BaseException`` gains a new mutable attribute ``__note__``, which defaults to
|
``BaseException`` gains a new mutable attribute ``__note__``, which defaults to
|
||||||
``None`` and may have a string assigned. When an exception with a note is displayed,
|
``None`` and may have a string assigned. When an exception with a note is
|
||||||
the note is displayed immediately after the exception.
|
displayed, the note is displayed immediately after the exception.
|
||||||
|
|
||||||
Assigning a new string value overrides an existing note; if concatenation is desired
|
Assigning a new string value overrides an existing note; if concatenation is
|
||||||
users are responsible for implementing it with e.g.::
|
desired users are responsible for implementing it with e.g.::
|
||||||
|
|
||||||
e.__note__ = msg if e.__note__ is None else e.__note__ + "\n" + msg
|
e.__note__ = msg if e.__note__ is None else e.__note__ + "\n" + msg
|
||||||
|
|
||||||
It is an error to assign a non-string-or-``None`` value to ``__note__``,
|
It is an error to assign a non-string-or-``None`` value to ``__note__``, or to
|
||||||
or to attempt to delete the attribute.
|
attempt to delete the attribute.
|
||||||
|
|
||||||
``BaseExceptionGroup.subgroup`` and ``BaseExceptionGroup.split``
|
``BaseExceptionGroup.subgroup`` and ``BaseExceptionGroup.split`` copy the
|
||||||
copy the ``__note__`` of the original exception group to the parts.
|
``__note__`` of the original exception group to the parts.
|
||||||
|
|
||||||
|
|
||||||
Backwards Compatibility
|
Backwards Compatibility
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
System-defined or "dunder" names (following the pattern ``__*__``) are part of the
|
System-defined or "dunder" names (following the pattern ``__*__``) are part of
|
||||||
language specification, with unassigned names reserved for future use and subject
|
the language specification, with `unassigned names reserved for future use and
|
||||||
to breakage without warning [4]_.
|
subject to breakage without warning
|
||||||
|
<https://docs.python.org/3/reference/lexical_analysis.html#reserved-classes-of-identifiers>`__.
|
||||||
|
|
||||||
We are also unaware of any code which *would* be broken by adding ``__note__``;
|
We are also unaware of any code which *would* be broken by adding ``__note__``;
|
||||||
assigning to a ``.__note__`` attribute already *works* on current versions of
|
assigning to a ``.__note__`` attribute already *works* on current versions of
|
||||||
Python - the note just won't be displayed with the traceback and exception message.
|
Python - the note just won't be displayed with the traceback and exception
|
||||||
|
message.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -156,16 +165,15 @@ How to Teach This
|
||||||
=================
|
=================
|
||||||
|
|
||||||
The ``__note__`` attribute will be documented as part of the language standard,
|
The ``__note__`` attribute will be documented as part of the language standard,
|
||||||
and explained as part of the tutorial "Errors and Exceptions" [5]_.
|
and explained as part of `the "Errors and Exceptions" tutorial
|
||||||
|
<https://github.com/python/cpython/pull/30441>`__.
|
||||||
|
|
||||||
|
|
||||||
Reference Implementation
|
Reference Implementation
|
||||||
========================
|
========================
|
||||||
|
|
||||||
``BaseException.__note__`` was implemented in [6]_ and released in CPython 3.11.0a3,
|
``BaseException.__note__`` was `implemented in <https://github.com/python/cpython/pull/29880>`__ and released in CPython
|
||||||
following discussions related to :pep:`654`. [7]_ [8]_ [9]_
|
3.11.0a3, following discussions related to :pep:`654`. [2]_
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Rejected Ideas
|
Rejected Ideas
|
||||||
|
@ -173,12 +181,13 @@ Rejected Ideas
|
||||||
|
|
||||||
Use ``print()`` (or ``logging``, etc.)
|
Use ``print()`` (or ``logging``, etc.)
|
||||||
--------------------------------------
|
--------------------------------------
|
||||||
Reporting explanatory or contextual information about an error by printing or logging
|
Reporting explanatory or contextual information about an error by printing or
|
||||||
has historically been an acceptable workaround. However, we dislike the way this
|
logging has historically been an acceptable workaround. However, we dislike
|
||||||
separates the content from the exception object it refers to - which can lead to
|
the way this separates the content from the exception object it refers to -
|
||||||
"orphan" reports if the error was caught and handled later, or merely significant
|
which can lead to "orphan" reports if the error was caught and handled later,
|
||||||
difficulties working out which explanation corresponds to which error.
|
or merely significant difficulties working out which explanation corresponds to
|
||||||
The new ``ExceptionGroup`` type intensifies these existing challenges.
|
which error. The new ``ExceptionGroup`` type intensifies these existing
|
||||||
|
challenges.
|
||||||
|
|
||||||
Keeping the ``__note__`` attached to the exception object, like the traceback,
|
Keeping the ``__note__`` attached to the exception object, like the traceback,
|
||||||
eliminates these problems.
|
eliminates these problems.
|
||||||
|
@ -186,19 +195,22 @@ eliminates these problems.
|
||||||
|
|
||||||
``raise Wrapper(explanation) from err``
|
``raise Wrapper(explanation) from err``
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
An alternative pattern is to use exception chaining: by raising a 'wrapper' exception
|
An alternative pattern is to use exception chaining: by raising a 'wrapper'
|
||||||
containing the context or explanation ``from`` the current exception, we avoid the
|
exception containing the context or explanation ``from`` the current exception,
|
||||||
separation challenges from ``print()``. However, this has two key problems.
|
we avoid the separation challenges from ``print()``. However, this has two key
|
||||||
|
problems.
|
||||||
|
|
||||||
First, it changes the type of the exception, which is often a breaking change for
|
First, it changes the type of the exception, which is often a breaking change
|
||||||
downstream code. We consider *always* raising a ``Wrapper`` exception unacceptably
|
for downstream code. We consider *always* raising a ``Wrapper`` exception
|
||||||
inelegant; but because custom exception types might have any number of required
|
unacceptably inelegant; but because custom exception types might have any
|
||||||
arguments we can't always create an instance of the *same* type with our explanation.
|
number of required arguments we can't always create an instance of the *same*
|
||||||
In cases where the exact exception type is known this can work, such as the standard
|
type with our explanation. In cases where the exact exception type is known
|
||||||
library ``http.client`` code [10]_, but not for libraries which call user code.
|
this can work, such as the standard library ``http.client`` `code
|
||||||
|
<https://github.com/python/cpython/blob/69ef1b59983065ddb0b712dac3b04107c5059735/Lib/http/client.py#L596-L597>`__,
|
||||||
|
but not for libraries which call user code.
|
||||||
|
|
||||||
Second, exception chaining reports several lines of additional detail, which are
|
Second, exception chaining reports several lines of additional detail, which
|
||||||
distracting for experienced users and can be very confusing for beginners.
|
are distracting for experienced users and can be very confusing for beginners.
|
||||||
For example, six of the eleven lines reported for this simple example relate to
|
For example, six of the eleven lines reported for this simple example relate to
|
||||||
exception chaining, and are unnecessary with ``BaseException.__note__``:
|
exception chaining, and are unnecessary with ``BaseException.__note__``:
|
||||||
|
|
||||||
|
@ -220,26 +232,26 @@ exception chaining, and are unnecessary with ``BaseException.__note__``:
|
||||||
File "example.py", line 6, in <module>
|
File "example.py", line 6, in <module>
|
||||||
raise AssertionError(why)
|
raise AssertionError(why)
|
||||||
AssertionError: Failed!
|
AssertionError: Failed!
|
||||||
# These lines are
|
# These lines are
|
||||||
The above exception was the direct cause of the following exception: # confusing for new
|
The above exception was the direct cause of ... # confusing for new
|
||||||
# users, and they
|
# users, and they
|
||||||
Traceback (most recent call last): # only exist due
|
Traceback (most recent call last): # only exist due
|
||||||
File "example.py", line 8, in <module> # to implementation
|
File "example.py", line 8, in <module> # to implementation
|
||||||
raise Explanation(msg) from e # constraints :-(
|
raise Explanation(msg) from e # constraints :-(
|
||||||
Explanation: # Hence this PEP!
|
Explanation: # Hence this PEP!
|
||||||
You can reproduce this error by ...
|
You can reproduce this error by ...
|
||||||
|
|
||||||
**In cases where these two problems do not apply, we encourage use
|
**In cases where these two problems do not apply, we encourage use of exception
|
||||||
of exception chaining rather than** ``__note__``.
|
chaining rather than** ``__note__``.
|
||||||
|
|
||||||
|
|
||||||
Subclass Exception and add ``__note__`` downstream
|
Subclass Exception and add ``__note__`` downstream
|
||||||
--------------------------------------------------
|
--------------------------------------------------
|
||||||
Traceback printing is built into the C code, and reimplemented in pure Python in
|
Traceback printing is built into the C code, and reimplemented in pure Python
|
||||||
traceback.py. To get ``err.__note__`` printed from a downstream implementation
|
in traceback.py. To get ``err.__note__`` printed from a downstream
|
||||||
would *also* require writing custom traceback-printing code; while this could
|
implementation would *also* require writing custom traceback-printing code;
|
||||||
be shared between projects and reuse some pieces of traceback.py we prefer to
|
while this could be shared between projects and reuse some pieces of
|
||||||
implement this once, upstream.
|
traceback.py we prefer to implement this once, upstream.
|
||||||
|
|
||||||
Custom exception types could implement their ``__str__`` method to include our
|
Custom exception types could implement their ``__str__`` method to include our
|
||||||
proposed ``__note__`` semantics, but this would be rarely and inconsistently
|
proposed ``__note__`` semantics, but this would be rarely and inconsistently
|
||||||
|
@ -248,50 +260,54 @@ applicable.
|
||||||
|
|
||||||
Store notes in ``ExceptionGroup``\ s
|
Store notes in ``ExceptionGroup``\ s
|
||||||
------------------------------------
|
------------------------------------
|
||||||
Initial discussions proposed making a more focussed change by thinking about how to
|
Initial discussions proposed making a more focussed change by thinking about
|
||||||
associate messages with the nested exceptions in ``ExceptionGroup`` s, such as a list
|
how to associate messages with the nested exceptions in ``ExceptionGroup`` s,
|
||||||
of notes or mapping of exceptions to notes. However, this would force a remarkably
|
such as a list of notes or mapping of exceptions to notes. However, this would
|
||||||
awkward API and retains a lesser form of the cross-referencing problem discussed
|
force a remarkably awkward API and retains a lesser form of the
|
||||||
under "use ``print()``" above; if this PEP is rejected we prefer the status quo.
|
cross-referencing problem discussed under "use ``print()``" above; if this PEP
|
||||||
Finally, of course, ``__note__`` is not only useful with ``ExceptionGroup`` s!
|
is rejected we prefer the status quo. Finally, of course, ``__note__`` is not
|
||||||
|
only useful with ``ExceptionGroup`` s!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Possible Future Enhancements
|
Possible Future Enhancements
|
||||||
============================
|
============================
|
||||||
|
|
||||||
In addition to rejected alternatives, there have been a range of suggestions which
|
In addition to rejected alternatives, there have been a range of suggestions
|
||||||
we believe should be deferred to a future version, when we have more experience with
|
which we believe should be deferred to a future version, when we have more
|
||||||
the uses (and perhaps misuses) of ``__note__``.
|
experience with the uses (and perhaps misuses) of ``__note__``.
|
||||||
|
|
||||||
|
|
||||||
Allow any object, and cast to string for display
|
Allow any object, and cast to string for display
|
||||||
------------------------------------------------
|
------------------------------------------------
|
||||||
We have not identified any scenario where libraries would want to do anything but either
|
We have not identified any scenario where libraries would want to do anything
|
||||||
concatenate or replace notes, and so the additional complexity and interoperability
|
but either concatenate or replace notes, and so the additional complexity and
|
||||||
challenges do not seem justified.
|
interoperability challenges do not seem justified.
|
||||||
|
|
||||||
Permitting any object would also force any future structured API to change the behaviour
|
Permitting any object would also force any future structured API to change the
|
||||||
of already-legal code, whereas expanding the permitted contents of ``__note__`` from strings
|
behaviour of already-legal code, whereas expanding the permitted contents of
|
||||||
to include other objects is fully backwards-compatible. In the absence of any proposed
|
``__note__`` from strings to include other objects is fully
|
||||||
use-case (see also `Non-goals`_), we prefer to begin with a restrictive API that can
|
backwards-compatible. In the absence of any proposed use-case (see also
|
||||||
be relaxed later.
|
`Non-goals`_), we prefer to begin with a restrictive API that can be relaxed
|
||||||
|
later.
|
||||||
|
|
||||||
We also note that converting an object to a string may raise an exception. It's more helpful
|
We also note that converting an object to a string may raise an exception.
|
||||||
for the traceback to point to the location where the note is attached to the exception,
|
It's more helpful for the traceback to point to the location where the note is
|
||||||
rather than where the exception and note are being formatted for display after propagation.
|
attached to the exception, rather than where the exception and note are being
|
||||||
|
formatted for display after propagation.
|
||||||
|
|
||||||
|
|
||||||
Add a helper function ``contextlib.add_exc_note()``
|
Add a helper function ``contextlib.add_exc_note()``
|
||||||
---------------------------------------------------
|
---------------------------------------------------
|
||||||
It was suggested [11]_ that we add a utility such as the one below to the standard
|
It `was suggested
|
||||||
library. We are open to this idea, but do not see it as a core part of the
|
<https://www.reddit.com/r/Python/comments/rmrvxv/pep_678_enriching_exceptions_with_notes/hptbul1/>`__
|
||||||
proposal of this PEP as it can be added as an enhancement later.
|
that we add a utility such as the one below to the standard library. We are
|
||||||
|
open to this idea, but do not see it as a core part of the proposal of this PEP
|
||||||
|
as it can be added as an enhancement later.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager def add_exc_note(note: str):
|
||||||
def add_exc_note(note: str):
|
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
|
@ -308,27 +324,21 @@ proposal of this PEP as it can be added as an enhancement later.
|
||||||
Augment the ``raise`` statement
|
Augment the ``raise`` statement
|
||||||
-------------------------------
|
-------------------------------
|
||||||
One discussion proposed ``raise Exception() with "note contents"``, but this
|
One discussion proposed ``raise Exception() with "note contents"``, but this
|
||||||
does not address the original motivation of compatibility with ``ExceptionGroup``.
|
does not address the original motivation of compatibility with
|
||||||
|
``ExceptionGroup``.
|
||||||
|
|
||||||
Furthermore, we do not believe that the problem we are solving requires or justifies
|
Furthermore, we do not believe that the problem we are solving requires or
|
||||||
new language syntax.
|
justifies new language syntax.
|
||||||
|
|
||||||
References
|
References
|
||||||
==========
|
==========
|
||||||
|
|
||||||
.. [1] https://discuss.python.org/t/accepting-pep-654-exception-groups-and-except/10813/26
|
.. [1] this principle was established in the 2003 mail thread which led to :pep:`3134`,
|
||||||
.. [2] https://bugs.python.org/issue46431
|
|
||||||
.. [3] this principle was established in the 2003 mail thread which led to :pep:`3134`,
|
|
||||||
and included a proposal for a group-of-exceptions type!
|
and included a proposal for a group-of-exceptions type!
|
||||||
https://mail.python.org/pipermail/python-dev/2003-January/032492.html
|
https://mail.python.org/pipermail/python-dev/2003-January/032492.html
|
||||||
.. [4] https://docs.python.org/3/reference/lexical_analysis.html#reserved-classes-of-identifiers
|
.. [2] particularly those at https://bugs.python.org/issue45607,
|
||||||
.. [5] https://github.com/python/cpython/pull/30441
|
https://discuss.python.org/t/accepting-pep-654-exception-groups-and-except/10813/9,
|
||||||
.. [6] https://github.com/python/cpython/pull/29880
|
https://github.com/python/cpython/pull/28569#discussion_r721768348, and
|
||||||
.. [7] https://discuss.python.org/t/accepting-pep-654-exception-groups-and-except/10813/9
|
|
||||||
.. [8] https://github.com/python/cpython/pull/28569#discussion_r721768348
|
|
||||||
.. [9] https://bugs.python.org/issue45607
|
|
||||||
.. [10] https://github.com/python/cpython/blob/69ef1b59983065ddb0b712dac3b04107c5059735/Lib/http/client.py#L596-L597
|
|
||||||
.. [11] https://www.reddit.com/r/Python/comments/rmrvxv/pep_678_enriching_exceptions_with_notes/hptbul1/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue