@@ -18,7 +18,7 @@ Exception objects are typically initialized with a message that describes the
18
18
error which has occurred. Because further information may be available when
19
19
the exception is caught and re-raised, or included in an ``ExceptionGroup ``,
20
20
this PEP proposes to add ``BaseException.add_note(note) ``, a
21
- ``.__notes__ `` attribute holding a tuple of zero or more notes so added, and to
21
+ ``.__notes__ `` attribute holding a list of notes so added, and to
22
22
update the builtin traceback formatting code to include notes in the formatted
23
23
traceback following the exception string.
24
24
@@ -50,9 +50,9 @@ exceptions. This is already error-prone, and made more difficult by :pep:`654`
50
50
``ExceptionGroup ``\ s, so the time is right for a built-in solution. We
51
51
therefore propose to add:
52
52
53
- - a new method ``BaseException.add_note(note) ``,
54
- - ``BaseException.__notes__ ``, a read-only attribute which is a tuple of zero or
55
- more note strings , and
53
+ - a new method ``BaseException.add_note(note: str ) ``,
54
+ - ``BaseException.__notes__ ``, a list of note strings added using
55
+ `` .add_note() `` , and
56
56
- support in the builtin traceback formatting code such that notes are
57
57
displayed in the formatted traceback following the exception string.
58
58
@@ -120,7 +120,7 @@ includes a note of the minimal failing example::
120
120
121
121
Non-goals
122
122
---------
123
- Tracking multiple notes as a tuple , rather than by concatenating strings when
123
+ Tracking multiple notes as a list , rather than by concatenating strings when
124
124
notes are added, is intended to maintain the distinction between the
125
125
individual notes. This might be required in specialized use cases, such
126
126
as translation of the notes by packages like ``friendly-traceback ``.
@@ -141,24 +141,29 @@ are collecting multiple exception objects to handle together. [1]_
141
141
Specification
142
142
=============
143
143
144
- ``BaseException `` gains a new new method ``.add_note(note: str) ``. Notes are
145
- exposed as a tuple via the read-only attribute ``__notes__ ``, and appear in
146
- the standard traceback after the exception string. ``.add_note(note) `` replaces
147
- ``__notes__ `` with a new tuple equal to ``__notes__ + (note,) ``, if ``note ``
148
- is a string, and raises ``TypeError `` otherwise.
144
+ ``BaseException `` gains a new method ``.add_note(note: str) ``. If ``note `` is
145
+ a string, ``.add_note(note) `` appends it to the ``__notes__ `` list, creating
146
+ the attribute if it does not already exist. If ``note `` is not a string,
147
+``.add_note() `` raises ``TypeError ``.
149
148
150
- `` del err.__notes__ `` clears the contents of the ``__notes__ `` attribute,
151
- leaving it an empty tuple as if `` .add_note() `` had never been called. This
152
- allows libraries full control over the attached notes, by re-adding whatever
153
- they wish, without overly complicating the API or adding multiple names to
149
+ Libraries may clear existing notes by modifying or deleting the ``__notes__ ``
150
+ list, if it has been created, including clearing all notes with
151
+ `` del err.__notes__ ``. This allows full control over the attached notes,
152
+ without overly complicating the API or adding multiple names to
154
153
``BaseException.__dict__ ``.
155
154
156
155
When an exception is displayed by the interpreter's builtin traceback-rendering code,
157
156
its notes (if there are any) appear immediately after the exception message, in the order
158
157
in which they were added, with each note starting on a new line.
159
158
160
- ``BaseExceptionGroup.subgroup `` and ``BaseExceptionGroup.split `` copy the
161
- ``__notes__ `` of the original exception group to the parts.
159
+ If ``__notes__ `` has been created, ``BaseExceptionGroup.subgroup `` and
160
+ ``BaseExceptionGroup.split `` create new lists on each new instance containing
161
+ the same contents as the original exception group.
162
+
163
+ We *do not * specify the expected behaviour when users have assigned a non-list
164
+ value to ``__notes__ ``, or a list which contains non-string elements.
165
+ Implementations might choose to emit warnings, discard or ignore bad values,
166
+ convert them to strings, raise an exception, or do something else entirely.
162
167
163
168
164
169
Backwards Compatibility
@@ -279,44 +284,13 @@ libraries such as ``friendly-traceback``, without resorting to dubious parsing
279
284
heuristics, we therefore settled on the ``.add_note() ``-and-``__notes__ `` API.
280
285
281
286
282
- Allow any object, and convert to string for display
283
- ---------------------------------------------------
284
- We have not identified any scenario where libraries would want to do anything
285
- but either concatenate or replace notes, and so the additional complexity and
286
- interoperability challenges do not seem justified.
287
-
288
- We also note that converting an object to a string may raise an exception.
289
- It's more helpful for the traceback to point to the location where the note is
290
- attached to the exception, rather than where the exception and note are being
291
- formatted for display after propagation.
292
-
293
-
294
- Just make ``__notes__ `` mutable
295
- -------------------------------
296
- If ``__notes__ `` was mutable (e.g. a list) or even assignable, there would be
297
- no need for explicit methods to add and remove notes separate from e.g.
298
- ``ex.__notes__.append(note) `` or ``ex.__notes__.clear() ``. While we like the
299
- simplicity of this approach, it cannot guarantee that ``__notes__ `` is a
300
- sequence of strings, and thus risks failing at traceback-display time like
301
- the proposal above.
302
-
303
-
304
- Separate methods for e.g. ``.clear_notes() ``
305
- --------------------------------------------
306
- We expect that clearing or replacing notes will be extremely rare, so while
307
- we wish to make this *possible *, we do not consider these APIs worth the cost.
308
- The ``del err.__notes__ `` pattern has precedent in e.g. invalidation of an
309
- ``@functools.cached_property ``, or any other descriptor with a deleter, and
310
- is sufficient for library code to implement whatever pattern is desired.
311
-
312
-
313
287
Subclass Exception and add note support downstream
314
288
--------------------------------------------------
315
289
Traceback printing is built into the C code, and reimplemented in pure Python
316
290
in ``traceback.py ``. To get ``err.__notes__ `` printed from a downstream
317
291
implementation
6D40
would *also * require writing custom traceback-printing code;
318
292
while this could be shared between projects and reuse some pieces of
319
- traceback.py we prefer to implement this once, upstream.
293
+ traceback.py [ 3 ]_ we prefer to implement this once, upstream.
320
294
321
295
Custom exception types could implement their ``__str__ `` method to include our
322
296
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,
335
309
are sufficient to justify the more general feature proposed by this PEP.
336
310
337
311
338
-
339
- Possible Future Enhancements
340
- ============================
341
-
342
- In addition to rejected alternatives, there have been a range of suggestions
343
- which we believe should be deferred to a future version, when we have more
344
- experience with the uses (and perhaps misuses) of ``__notes__ ``.
345
-
346
-
347
312
Add a helper function ``contextlib.add_exc_note() ``
348
313
---------------------------------------------------
349
314
It `was suggested
350
315
<https://www.reddit.com/r/Python/comments/rmrvxv/pep_678_enriching_exceptions_with_notes/hptbul1/> `__
351
- that we add a utility such as the one below to the standard library. We are
352
- open to this idea, but do not see it as a core part of the proposal of this PEP
353
- as it can be added as an enhancement later.
316
+ that we add a utility such as the one below to the standard library. We do not
317
+ see this idea as core to the proposal of this PEP, and thus leave it for later
318
+ or downstream implementation - perhaps based on this example code:
354
319
355
320
.. code-block :: python
356
321
@@ -381,9 +346,9 @@ Acknowledgements
381
346
We wish to thank the many people who have assisted us through conversation,
382
347
code review, design advice, and implementation: Adam Turner, Alex Grönholm,
383
348
André Roberge, Barry Warsaw, Brett Cannon, CAM Gerlach, Carol Willing, Damian,
384
- Erlend Aasland, Gregory Smith, Guido van Rossum, Irit Katriel, Jelle Zijlstra ,
385
- Ken Jin, Kumar Aditya, Mark Shannon, Matti Picus, Petr Viktorin,
386
- and pseudonymous commenters on Discord and Reddit.
349
+ Erlend Aasland, Etienne Pot, Gregory Smith, Guido van Rossum, Irit Katriel,
350
+ Jelle Zijlstra, Ken Jin, Kumar Aditya, Mark Shannon, Matti Picus, Petr
351
+ Viktorin, Will McGugan, and pseudonymous commenters on Discord and Reddit.
387
352
388
353
389
354
References
@@ -395,7 +360,11 @@ References
395
360
.. [2 ] particularly those at https://bugs.python.org/issue45607,
396
361
https://discuss.python.org/t/accepting-pep-654-exception-groups-and-except/10813/9,
397
362
https://github.com/python/cpython/pull/28569#discussion_r721768348, and
398
-
363
+ .. [3 ] We note that the ``exceptiongroup `` backport package maintains an exception
364
+ hook and monkeypatch for ``TracebackException `` for Pythons older than 3.11,
365
+ and encourage library authors to avoid creating additional and incompatible
366
+ backports. We also reiterate our preference for builtin support which
367
+ makes such measures unnecessary.
399
368
400
369
401
370
Copyright
0 commit comments