8000 PEP 678: simplify specification to use a list by Zac-HD · Pull Request #2456 · python/peps · GitHub
[go: up one dir, main page]

Skip to content

PEP 678: simplify specification to use a list #2456

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 24, 2022
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
PEP-678: simplify specification
  • Loading branch information
Zac-HD committed Mar 22, 2022
commit 413dcadbfcfd5c431b8ff456f01f70a8ae309dcb
92 changes: 28 additions & 64 deletions pep-0678.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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``.
Expand All @@ -141,24 +141,24 @@ 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 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.


Backwards Compatibility
Expand Down Expand Up @@ -279,44 +279,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
Expand All @@ -335,22 +304,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

Expand Down Expand Up @@ -381,9 +341,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
Expand All @@ -395,7 +355,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
Expand Down
0