@@ -15,13 +15,14 @@ Abstract
15
15
========
16
16
Exception objects are typically initialized with a message that describes the
17
17
error which has occurred. Because further information may be available when the
18
- exception is caught and re-raised, this PEP proposes to add a `` .__note__ ``
19
- attribute and update the builtin traceback formatting code to include it in
20
- the formatted traceback following the exception string.
18
+ exception is caught and re-raised, or included in an `` ExceptionGroup ``, this PEP
19
+ proposes to add a `` .__note__ `` attribute and update the builtin traceback formatting
20
+ code to include it in the formatted traceback following the exception string.
21
21
22
- This is particularly useful in relation to :pep: `654 ` ``ExceptionGroup `` s, which
22
+ This is particularly useful in relation to :pep: `654 ` ``ExceptionGroup ``\ s, which
23
23
make previous workarounds ineffective or confusing. Use cases have been identified
24
- in the standard library, Hypothesis package, and common code patterns with retries.
24
+ in the standard library, Hypothesis and ``cattrs `` packages, and common code
25
+ patterns with retries.
25
26
26
27
27
28
Motivation
@@ -32,17 +33,18 @@ where it is useful to add information after the exception was caught.
32
33
For example,
33
34
34
35
- testing libraries may wish to show the values involved in a failing assertion,
35
- or the steps to reproduce a failure (e.g. ``pytest `` and ``hypothesis `` ; example below).
36
- - code with retries may wish to note which iteration or timestamp raised which
37
- error - especially if re-raising them in an ``ExceptionGroup ``
36
+ or the steps to reproduce a failure (e.g. ``pytest `` and ``hypothesis ``; example below).
37
+ - code which retries an operation on error may wish to associate an iteration, timestamp,
38
+ or other explanation with each of several errors - especially if re-raising them in
39
+ an ``ExceptionGroup ``.
38
40
- programming environments for novices can provide more detailed descriptions
39
- of various errors, and tips for resolving them (e.g. ``friendly-traceback `` ).
41
+ of various errors, and tips for resolving them (e.g. ``friendly-traceback ``).
40
42
41
43
Existing approaches must pass this additional information around while keeping
42
44
it in sync with the state of raised, and potentially caught or chained, exceptions.
43
- This is already error-prone, and made more difficult by :pep: `654 ` ``ExceptionGroup `` s,
45
+ This is already error-prone, and made more difficult by :pep: `654 ` ``ExceptionGroup ``\ s,
44
46
so the time is right for a built-in solution. We therefore propose to add a mutable
45
- field ``__note__ `` to ``BaseException `` , which can be assigned a string - and
47
+ field ``__note__ `` to ``BaseException ``, which can be assigned a string - and
46
48
if assigned, is automatically displayed in formatted tracebacks.
47
49
48
50
@@ -106,10 +108,21 @@ exception includes a note of the minimal failing example::
106
108
+------------------------------------
107
109
108
110
111
+ Non-goals
112
+ ---------
113
+ ``__note__ `` is *not * intended to carry structured data. If your note is for use by
114
+ a program rather than display to a human, we recommend instead choosing a convention
115
+ for an attribute like e.g. ``err._parse_errors = ... `` on the error or ``ExceptionGroup `` [1 ]_ [2 ]_
116
+
117
+ As a rule of thumb, prefer `exception chaining <https://docs.python.org/3/tutorial/errors.html#exception-chaining >`__
118
+ when the error is going to be re-raised or handled as an individual error, and prefer
119
+ ``__note__ `` when you are collecting multiple exception objects to handle together or later. [3 ]_
120
+
121
+
109
122
Specification
110
123
=============
111
124
112
- ``BaseException `` gains a new mutable attribute ``__note__ `` , which defaults to
125
+ ``BaseException `` gains a new mutable attribute ``__note__ ``, which defaults to
113
126
``None `` and may have a string assigned. When an exception with a note is displayed,
114
127
the note is displayed immediately after the exception.
115
128
@@ -118,7 +131,7 @@ users are responsible for implementing it with e.g.::
118
131
119
132
e.__note__ = msg if e.__note__ is None else e.__note__ + "\n" + msg
120
133
121
- It is an error to assign a non-string-or-``None `` value to ``__note__ `` ,
134
+ It is an error to assign a non-string-or-``None `` value to ``__note__ ``,
122
135
or to attempt to delete the attribute.
123
136
124
137
``BaseExceptionGroup.subgroup `` and ``BaseExceptionGroup.split ``
@@ -128,11 +141,11 @@ copy the ``__note__`` of the original exception group to the parts.
128
141
Backwards Compatibility
129
142
=======================
130
143
131
- System-defined or "dunder" names (following the pattern ``__*__ `` ) are part of the
144
+ System-defined or "dunder" names (following the pattern ``__*__ ``) are part of the
132
145
language specification, with unassigned names reserved for future use and subject
133
- to breakage without warning [1 ]_.
146
+ to breakage without warning [4 ]_.
134
147
135
- We are also unaware of any code which *would * be broken by adding ``__note__ `` ;
148
+ We are also unaware of any code which *would * be broken by adding ``__note__ ``;
136
149
assigning to a ``.__note__ `` attribute already *works * on current versions of
137
150
Python - the note just won't be displayed with the traceback and exception message.
138
151
@@ -142,23 +155,23 @@ How to Teach This
142
155
=================
143
156
144
157
The ``__note__ `` attribute will be documented as part of the language standard,
145
- and explained as part of the tutorial "Errors and Exceptions" [2 ]_.
158
+ and explained as part of the tutorial "Errors and Exceptions" [5 ]_.
146
159
147
160
148
161
149
162
Reference Implementation
150
163
========================
151
164
152
- ``BaseException.__note__ `` was implemented in [3 ]_ and released in CPython 3.11.0a3,
153
- following discussions related to :pep: `654 `. [4 ]_ [5 ]_ [6 ]_
165
+ ``BaseException.__note__ `` was implemented in [6 ]_ and released in CPython 3.11.0a3,
166
+ following discussions related to :pep: `654 `. [7 ]_ [8 ]_ [9 ]_
154
167
155
168
156
169
157
170
Rejected Ideas
158
171
==============
159
172
160
- Use ``print() `` (or ``logging `` , etc.)
161
- ---------------------------------------
173
+ Use ``print() `` (or ``logging ``, etc.)
174
+ --------------------------------------
162
175
Reporting explanatory or contextual information about an error by printing or logging
163
176
has historically been an acceptable workaround. However, we dislike the way this
164
177
separates the content from the exception object it refers to - which can lead to
@@ -174,19 +187,19 @@ eliminates these problems.
174
187
---------------------------------------
175
188
An alternative pattern is to use exception chaining: by raising a 'wrapper' exception
176
189
containing the context or explanation ``from `` the current exception, we avoid the
177
- separation challenges from ``print() `` . However, this has two key problems.
190
+ separation challenges from ``print() ``. However, this has two key problems.
178
191
179
192
First, it changes the type of the exception, which is often a breaking change for
180
193
downstream code. We consider *always * raising a ``Wrapper `` exception unacceptably
181
194
inelegant; but because custom exception types might have any number of required
182
195
arguments we can't always create an instance of the *same * type with our explanation.
183
196
In cases where the exact exception type is known this can work, such as the standard
184
- library ``http.client `` code [7 ]_, but not for libraries which call user code.
197
+ library ``http.client `` code [10 ]_, but not for libraries which call user code.
185
198
186
199
Second, exception chaining reports several lines of additional detail, which are
187
200
distracting for experienced users and can be very confusing for beginners.
188
201
For example, six of the eleven lines reported for this simple example relate to
189
- exception chaining, and are unnecessary with ``BaseException.__note__ `` :
202
+ exception chaining, and are unnecessary with ``BaseException.__note__ ``:
190
203
191
204
.. code-block :: python
192
205
@@ -215,6 +228,9 @@ exception chaining, and are unnecessary with ``BaseException.__note__`` :
215
228
Explanation: # Hence this PEP!
216
229
You can reproduce this error by ...
217
230
231
+ **In cases where these two problems do not apply, we encourage use
232
+ of exception chaining rather than ** ``__note__ ``.
233
+
218
234
219
235
Subclass Exception and add ``__note__ `` downstream
220
236
--------------------------------------------------
@@ -229,27 +245,84 @@ proposed ``__note__`` semantics, but this would be rarely and inconsistently
229
245
applicable.
230
246
231
247
232
- Store notes in ``ExceptionGroup `` s
233
- -----------------------------------
248
+ Store notes in ``ExceptionGroup ``\ s
249
+ ------------------------------------
234
250
Initial discussions proposed making a more focussed change by thinking about how to
235
251
associate messages with the nested exceptions in ``ExceptionGroup `` s, such as a list
236
252
of notes or mapping of exceptions to notes. However, this would force a remarkably
237
253
awkward API and retains a lesser form of the cross-referencing problem discussed
238
- under "use ``print() `` " above; if this PEP is rejected we prefer the status quo.
254
+ under "use ``print() ``" above; if this PEP is rejected we prefer the status quo.
239
255
Finally, of course, ``__note__ `` is not only useful with ``ExceptionGroup `` s!
240
256
241
257
242
258
259
+ Possible Future Enhancements
260
+ ============================
261
+
262
+ In addition to rejected alternatives, there have been a range of suggestions which
263
+ we believe should be deferred to a future version, when we have more experience with
264
+ the uses (and perhaps misuses) of ``__note__ ``.
265
+
266
+
267
+ Allow any object, and cast to string for display
268
+ ------------------------------------------------
269
+ We have not identified any scenario where libraries would want to do anything but either
270
+ concatenate or replace notes, and so the additional complexity and interoperability
271
+ challenges do not seem justified.
272
+
273
+ Permitting any object would also force any future structured API to change the behaviour
274
+ of already-legal code, whereas expanding the permitted contents of ``__note__ `` from strings
275
+ to include other objects is fully backwards-compatible. In the absence of any proposed
276
+ use-case (see also `Non-goals `_), we prefer to begin with a restrictive API that can
277
+ be relaxed later.
278
+
279
+
280
+ Add a helper function ``contextlib.add_exc_note() ``
281
+ ---------------------------------------------------
282
+ It was suggested that we add a utility such as the one below to the standard
283
+ library. We are open to this idea, but do not see it as a core part of the
284
+ proposal of this PEP as it can be added as an enhancement later.
285
+
286
+ .. code-block :: python
287
+
288
+ @contextlib.contextmanager
289
+ def add_exc_note (note : str ):
290
+ try :
291
+ yield
292
+ except Exception as err:
293
+ if err.__note__ is None :
294
+ err.__note__ = note
295
+ else :
296
+ err.__note__ = err.__note__ + " \n\n " + note
297
+ raise
298
+
299
+ with add_exc_note(f " While attempting to frobnicate { item= } " ):
300
+ frobnicate_or_raise(item)
301
+
302
+
303
+ Augment the ``raise `` statement
304
+ -------------------------------
305
+ One discussion proposed ``raise Exception() with "note contents" ``, but this
306
+ does not address the original motivation of compatibility with ``ExceptionGroup ``.
307
+
308
+ Furthermore, we do not believe that the problem we are solving requires or justifies
309
+ new language syntax.
310
+
243
311
References
244
312
==========
245
313
246
- .. [1 ] https://docs.python.org/3/reference/lexical_analysis.html#reserved-classes-of-identifiers
247
- .. [2 ] https://github.com/python/cpython/pull/30158
248
- .. [3 ] https://github.com/python/cpython/pull/29880
249
- .. [4 ] https://discuss.python.org/t/accepting-pep-654-exception-groups-and-except/10813/9
250
- .. [5 ] https://github.com/python/cpython/pull/28569#discussion_r721768348
251
- .. [6 ] https://bugs.python.org/issue45607
252
- .. [7 ] https://github.com/python/cpython/blob/69ef1b59983065ddb0b712dac3b04107c5059735/Lib/http/client.py#L596-L597
314
+ .. [1 ] https://discuss.python.org/t/accepting-pep-654-exception-groups-and-except/10813/26
315
+ .. [2 ] https://bugs.python.org/issue46431
316
+ .. [3 ] this principle was established in the 2003 mail thread which led to :pep: `3134 `,
317
+ and included a proposal for a group-of-exceptions type!
318
+ https://mail.python.org/pipermail/python-dev/2003-January/032492.html
319
+ .. [4 ] https://docs.python.org/3/reference/lexical_analysis.html#reserved-classes-of-identifiers
320
+ .. [5 ] https://github.com/python/cpython/pull/30158
321
+ .. [6 ] https://github.com/python/cpython/pull/29880
322
+ .. [7 ] https://discuss.python.org/t/accepting-pep-654-exception-groups-and-except/10813/9
323
+ .. [8 ] https://github.com/python/cpython/pull/28569#discussion_r721768348
324
+ .. [9 ] https://bugs.python.org/issue45607
325
+ .. [10 ] https://github.com/python/cpython/blob/69ef1b59983065ddb0b712dac3b04107c5059735/Lib/http/client.py#L596-L597
253
326
254
327
255
328
0 commit comments