PEP 678: `del err.__notes__`, and why we're not just using a list (#2360)

Co-authored-by: CAM Gerlach <CAM.Gerlach@Gerlach.CAM>
This commit is contained in:
Zac Hatfield-Dodds 2022-02-24 23:24:00 -08:00 committed by GitHub
parent e846b706bb
commit 7d3a7b9ebe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 45 additions and 23 deletions

View File

@ -17,7 +17,7 @@ Abstract
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, *, replace=False)``, a
this PEP proposes to add ``BaseException.add_note(note)``, a
``.__notes__`` attribute holding a tuple of zero or more notes so added, and to
update the builtin traceback formatting code to include notes in the formatted
traceback following the exception string.
@ -50,8 +50,8 @@ 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, *, replace=False)``,
- ``BaseException.__notes__``, a read-only field which is a tuple of zero or
- a new method ``BaseException.add_note(note)``,
- ``BaseException.__notes__``, a read-only attribute which is a tuple of zero or
more note strings, and
- support in the builtin traceback formatting code such that notes are
displayed in the formatted traceback following the exception string.
@ -141,12 +141,15 @@ Specification
=============
``BaseException`` gains a new read-only attribute ``__notes__``, an initially
empty tuple, and a new method ``.add_note(note: str | None, *, replace:
bool=False)``. If ``note`` is not ``None``, it is added to the exception's
notes which appear in the standard traceback after the exception string. If
``replace`` is true, all previously existing notes are removed before the new
one is added. To clear all notes, use ``add_note(None, replace=True)``. A
``TypeError`` is raised if ``note`` is neither a string nor ``None``.
empty tuple, and a new method ``.add_note(note: str)``. ``note`` is added to
the exception's notes, which appear in the standard traceback after the
exception string. A ``TypeError`` is raised if ``note`` is not a string.
``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
``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
@ -261,8 +264,8 @@ exception chaining, and are unnecessary with ``BaseException.add_note()``:
chaining rather than** ``__notes__``.
A mutable ``__note__`` attribute
--------------------------------
An assignable ``__note__`` attribute
------------------------------------
The first draft and implementation of this PEP defined a single attribute
``__note__``, which defaulted to ``None`` but could have a string assigned.
This is substantially simpler if, and only if, there is at most one note.
@ -272,6 +275,37 @@ 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
@ -305,18 +339,6 @@ which we believe should be deferred to a future version, when we have more
experience with the uses (and perhaps misuses) of ``__notes__``.
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.
Add a helper function ``contextlib.add_exc_note()``
---------------------------------------------------
It `was suggested