diff --git a/pep-0678.rst b/pep-0678.rst index e4df45f7d55..6fdba97bbd9 100644 --- a/pep-0678.rst +++ b/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 `__ -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