PEP 678: simplify specification to use a list and create the __notes__ attr when it is first populated (GH-2456)

This commit is contained in:
Zac Hatfield-Dodds 2022-03-24 00:17:20 -07:00 committed by GitHub
parent 4370f8f737
commit 25fa660908
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 33 additions and 64 deletions

View File

@ -18,7 +18,7 @@ Exception objects are typically initialized with a message that describes the
error which has occurred. Because further information may be available when
the exception is caught and re-raised, or included in an ``ExceptionGroup``,
this PEP proposes to add ``BaseException.add_note(note)``, a
``.__notes__`` attribute holding a tuple of zero or more notes so added, and to
``.__notes__`` attribute holding a list of notes so added, and to
update the builtin traceback formatting code to include notes in the formatted
traceback following the exception string.
@ -50,9 +50,9 @@ exceptions. This is already error-prone, and made more difficult by :pep:`654`
``ExceptionGroup``\ s, so the time is right for a built-in solution. We
therefore propose to add:
- a new method ``BaseException.add_note(note)``,
- ``BaseException.__notes__``, a read-only attribute which is a tuple of zero or
more note strings, and
- a new method ``BaseException.add_note(note: str)``,
- ``BaseException.__notes__``, a list of note strings added using
``.add_note()``, and
- support in the builtin traceback formatting code such that notes are
displayed in the formatted traceback following the exception string.
@ -120,7 +120,7 @@ includes a note of the minimal failing example::
Non-goals
---------
Tracking multiple notes as a tuple, rather than by concatenating strings when
Tracking multiple notes as a list, rather than by concatenating strings when
notes are added, is intended to maintain the distinction between the
individual notes. This might be required in specialized use cases, such
as translation of the notes by packages like ``friendly-traceback``.
@ -141,24 +141,29 @@ are collecting multiple exception objects to handle together. [1]_
Specification
=============
``BaseException`` gains a new new method ``.add_note(note: str)``. Notes are
exposed as a tuple via the read-only attribute ``__notes__``, and appear in
the standard traceback after the exception string. ``.add_note(note)`` replaces
``__notes__`` with a new tuple equal to ``__notes__ + (note,)``, if ``note``
is a string, and raises ``TypeError`` otherwise.
``BaseException`` gains a new method ``.add_note(note: str)``. If ``note`` is
a string, ``.add_note(note)`` appends it to the ``__notes__`` list, creating
the attribute if it does not already exist. If ``note`` is not a string,
``.add_note()`` raises ``TypeError``.
``del err.__notes__`` clears the contents of the ``__notes__`` attribute,
leaving it an empty tuple as if ``.add_note()`` had never been called. This
allows libraries full control over the attached notes, by re-adding whatever
they wish, without overly complicating the API or adding multiple names to
Libraries may clear existing notes by modifying or deleting the ``__notes__``
list, if it has been created, including clearing all notes with
``del err.__notes__``. This allows full control over the attached notes,
without overly complicating the API or adding multiple names to
``BaseException.__dict__``.
When an exception is displayed by the interpreter's builtin traceback-rendering code,
its notes (if there are any) appear immediately after the exception message, in the order
in which they were added, with each note starting on a new line.
``BaseExceptionGroup.subgroup`` and ``BaseExceptionGroup.split`` copy the
``__notes__`` of the original exception group to the parts.
If ``__notes__`` has been created, ``BaseExceptionGroup.subgroup`` and
``BaseExceptionGroup.split`` create new lists on each new instance containing
the same contents as the original exception group.
We *do not* specify the expected behaviour when users have assigned a non-list
value to ``__notes__``, or a list which contains non-string elements.
Implementations might choose to emit warnings, discard or ignore bad values,
convert them to strings, raise an exception, or do something else entirely.
Backwards Compatibility
@ -279,44 +284,13 @@ libraries such as ``friendly-traceback``, without resorting to dubious parsing
heuristics, we therefore settled on the ``.add_note()``-and-``__notes__`` API.
Allow any object, and convert to string for display
---------------------------------------------------
We have not identified any scenario where libraries would want to do anything
but either concatenate or replace notes, and so the additional complexity and
interoperability challenges do not seem justified.
We also note that converting an object to a string may raise an exception.
It's more helpful for the traceback to point to the location where the note is
attached to the exception, rather than where the exception and note are being
formatted for display after propagation.
Just make ``__notes__`` mutable
-------------------------------
If ``__notes__`` was mutable (e.g. a list) or even assignable, there would be
no need for explicit methods to add and remove notes separate from e.g.
``ex.__notes__.append(note)`` or ``ex.__notes__.clear()``. While we like the
simplicity of this approach, it cannot guarantee that ``__notes__`` is a
sequence of strings, and thus risks failing at traceback-display time like
the proposal above.
Separate methods for e.g. ``.clear_notes()``
--------------------------------------------
We expect that clearing or replacing notes will be extremely rare, so while
we wish to make this *possible*, we do not consider these APIs worth the cost.
The ``del err.__notes__`` pattern has precedent in e.g. invalidation of an
``@functools.cached_property``, or any other descriptor with a deleter, and
is sufficient for library code to implement whatever pattern is desired.
Subclass Exception and add note support downstream
--------------------------------------------------
Traceback printing is built into the C code, and reimplemented in pure Python
in ``traceback.py``. To get ``err.__notes__`` printed from a downstream
implementation would *also* require writing custom traceback-printing code;
while this could be shared between projects and reuse some pieces of
traceback.py we prefer to implement this once, upstream.
traceback.py [3]_ we prefer to implement this once, upstream.
Custom exception types could implement their ``__str__`` method to include our
proposed ``__notes__`` semantics, but this would be rarely and inconsistently
@ -335,22 +309,13 @@ We believe that the cleaner interface, and other use-cases described above,
are sufficient to justify the more general feature proposed by this PEP.
Possible Future Enhancements
============================
In addition to rejected alternatives, there have been a range of suggestions
which we believe should be deferred to a future version, when we have more
experience with the uses (and perhaps misuses) of ``__notes__``.
Add a helper function ``contextlib.add_exc_note()``
---------------------------------------------------
It `was suggested
<https://www.reddit.com/r/Python/comments/rmrvxv/pep_678_enriching_exceptions_with_notes/hptbul1/>`__
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.
that we add a utility such as the one below to the standard library. We do not
see this idea as core to the proposal of this PEP, and thus leave it for later
or downstream implementation - perhaps based on this example code:
.. code-block:: python
@ -381,9 +346,9 @@ Acknowledgements
We wish to thank the many people who have assisted us through conversation,
code review, design advice, and implementation: Adam Turner, Alex Grönholm,
André Roberge, Barry Warsaw, Brett Cannon, CAM Gerlach, Carol Willing, Damian,
Erlend Aasland, Gregory Smith, Guido van Rossum, Irit Katriel, Jelle Zijlstra,
Ken Jin, Kumar Aditya, Mark Shannon, Matti Picus, Petr Viktorin,
and pseudonymous commenters on Discord and Reddit.
Erlend Aasland, Etienne Pot, Gregory Smith, Guido van Rossum, Irit Katriel,
Jelle Zijlstra, Ken Jin, Kumar Aditya, Mark Shannon, Matti Picus, Petr
Viktorin, Will McGugan, and pseudonymous commenters on Discord and Reddit.
References
@ -395,7 +360,11 @@ References
.. [2] particularly those at https://bugs.python.org/issue45607,
https://discuss.python.org/t/accepting-pep-654-exception-groups-and-except/10813/9,
https://github.com/python/cpython/pull/28569#discussion_r721768348, and
.. [3] We note that the ``exceptiongroup`` backport package maintains an exception
hook and monkeypatch for ``TracebackException`` for Pythons older than 3.11,
and encourage library authors to avoid creating additional and incompatible
backports. We also reiterate our preference for builtin support which
makes such measures unnecessary.
Copyright