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:
parent
4370f8f737
commit
25fa660908
97
pep-0678.rst
97
pep-0678.rst
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue