@@ -17,7 +17,7 @@ As :pep:`654` :class:`ExceptionGroup` has come into widespread use across the
17
17
Python community, some common but awkward patterns have emerged. We therefore
18
18
propose adding two new methods to exception objects:
19
19
20
- - :meth: `!BaseExceptionGroup.flat_exceptions `, returning the 'leaf' exceptions as
20
+ - :meth: `!BaseExceptionGroup.leaf_exceptions `, returning the 'leaf' exceptions as
21
21
a list, with each traceback composited from any intermediate groups.
22
22
23
23
- :meth: `!BaseException.preserve_context `, a context manager which
@@ -39,10 +39,10 @@ often write code to process or respond to individual leaf exceptions, for
39
39
example when implementing middleware, error logging, or response handlers in
40
40
a web framework.
41
41
42
- `Searching GitHub `__ found four implementations of :meth: `!flat_exceptions ` by
42
+ `Searching GitHub `__ found four implementations of :meth: `!leaf_exceptions ` by
43
43
various names in the first sixty hits, of which none handle
44
44
tracebacks.\ [#numbers ]_ The same search found thirteen cases where
45
- :meth: `!.flat_exceptions ` could be used. We therefore believe that providing
45
+ :meth: `!.leaf_exceptions ` could be used. We therefore believe that providing
46
46
a method on the :class: `BaseException ` type with proper traceback preservation
47
47
will improve error-handling and debugging experiences across the ecosystem.
48
48
@@ -55,7 +55,7 @@ unwrap ``HTTPException`` if that is the sole leaf of a group:
55
55
.. code-block :: python
56
56
57
57
except * HTTPException as group:
58
- first, * rest = group.flat_exceptions () # get the whole traceback :-)
58
+ first, * rest = group.leaf_exceptions () # get the whole traceback :-)
59
59
if not rest:
60
60
raise first
61
61
raise
@@ -78,12 +78,12 @@ readable, and easy-to-use solution for these cases.
78
78
Specification
79
79
=============
80
80
81
- A new method ``flat_exceptions () `` will be added to ``BaseExceptionGroup ``, with the
81
+ A new method ``leaf_exceptions () `` will be added to ``BaseExceptionGroup ``, with the
82
82
following signature:
83
83
84
84
.. code-block :: python
85
85
86
- def flat_exceptions (self , * , fix_tracebacks = True ) -> list[BaseException ]:
86
+ def leaf_exceptions (self , * , fix_tracebacks = True ) -> list[BaseException ]:
87
87
"""
88
88
Return a flat list of all 'leaf' exceptions in the group.
89
89
@@ -118,7 +118,7 @@ Usage example:
118
118
try :
119
119
user_code_here()
120
120
except * HTTPException as group:
121
- first, * rest = group.flat_exceptions ()
121
+ first, * rest = group.leaf_exceptions ()
122
122
if rest:
123
123
raise # handled by internal-server-error middleware
124
124
... # logging, cache updates, etc.
@@ -142,12 +142,12 @@ Backwards Compatibility
142
142
143
143
Adding new methods to built-in classes, especially those as widely used as
144
144
``BaseException ``, can have substantial impacts. However, GitHub search shows
145
- no collisions for these method names (`zero hits `__ and
145
+ no collisions for these method names (`zero hits `__\ [ #naming ]_ and
146
146
`three unrelated hits `__ respectively). If user-defined methods with these
147
147
names exist in private code they will shadow those proposed in the PEP,
148
148
without changing runtime behavior.
149
149
150
- __ https://github.com/search?q=%2F%5C.flat_exceptions %5C%28%2F+language%3APython&type=code
150
+ __ https://github.com/search?q=%2F%5C.leaf_exceptions %5C%28%2F+language%3APython&type=code
151
151
__ https://github.com/search?q=%2F%5C.preserve_context%5C%28%2F+language%3APython&type=code
152
152
153
153
@@ -157,20 +157,20 @@ How to Teach This
157
157
Working with exception groups is an intermediate-to-advanced topic, unlikely
158
158
to arise for beginning programmers. We therefore suggest teaching this topic
159
159
via documentation, and via just-in-time feedback from static analysis tools.
160
- In intermediate classes, we recommend teaching ``.flat_exceptions () `` together
160
+ In intermediate classes, we recommend teaching ``.leaf_exceptions () `` together
161
161
with the ``.split() `` and ``.subgroup() `` methods, and mentioning
162
162
``.preserve_context() `` as an advanced option to address specific pain points.
163
163
164
164
Both the API reference and the existing `ExceptionGroup tutorial `__
165
165
should be updated to demonstrate and explain the new methods. The tutorial
166
- should include examples of common patterns where ``.flat_exceptions () `` and
166
+ should include examples of common patterns where ``.leaf_exceptions () `` and
167
167
``.preserve_context() `` help simplify error handling logic. Downstream
168
168
libraries which often use exception groups could include similar docs.
169
169
170
170
__ https://docs.python.org/3/tutorial/errors.html#raising-and-handling-multiple-unrelated-exceptions
171
171
172
172
We have also designed lint rules for inclusion in ``flake8-async `` which will
173
- suggest using ``.flat_exceptions () `` when iterating over ``group.exceptions ``
173
+ suggest using ``.leaf_exceptions () `` when iterating over ``group.exceptions ``
174
174
or re-raising a leaf exception, and suggest using ``.preserve_context() `` when
175
175
re-raising a leaf exception inside an ``except* `` block would override any
176
176
existing context.
@@ -186,7 +186,7 @@ on older versions of Python, and can demonstrate the intended semantics.
186
186
We have found these helper functions quite useful when working with
187
187
:class: `ExceptionGroup `\ s in a large production codebase.
188
188
189
- A ``flat_exceptions () `` helper function
189
+ A ``leaf_exceptions () `` helper function
190
190
---------------------------------------
191
191
192
192
.. code-block :: python
@@ -196,7 +196,7 @@ A ``flat_exceptions()`` helper function
196
196
from types import TracebackType
197
197
198
198
199
- def flat_exceptions (
199
+ def leaf_exceptions (
200
200
self : BaseExceptionGroup, * , fix_traceback : bool = True
201
201
) -> list[BaseException ]:
202
202
"""
@@ -297,11 +297,11 @@ Add ``BaseException.as_group()`` (or group methods)
297
297
Our survey of ``ExceptionGroup ``-related error handling code also observed
298
298
many cases of duplicated logic to handle both a bare exception, and the same
299
299
kind of exception inside a group (often incorrectly, motivating
300
- ``.flat_exceptions () ``).
300
+ ``.leaf_exceptions () ``).
301
301
302
302
We briefly `proposed <https://github.com/python/cpython/issues/125825 >`__
303
303
adding ``.split(...) `` and ``.subgroup(...) `` methods too all exceptions,
304
- before considering ``.flat_exceptions () `` made us feel this was too clumsy.
304
+ before considering ``.leaf_exceptions () `` made us feel this was too clumsy.
305
305
As a cleaner alternative, we sketched out an ``.as_group() `` method:
306
306
307
307
.. code-block :: python
@@ -329,6 +329,20 @@ less magical and tempting to use in cases where it would not be appropriate.
329
329
We could be argued around though, if others prefer this form.
330
330
331
331
332
+ Preserve additional attributes
333
+ ------------------------------
334
+
335
+ We decided against preserving the ``__cause__ `` and ``__suppress_context__ ``
336
+ attributes, because they are not changed by re-raising the exception, and we
337
+ prefer to support ``raise exc from None `` or ``raise exc from cause_exc ``
338
+ together with ``with exc.preserve_context(): ``.
339
+
340
+ Similarly, we considered preserving the ``__traceback__ `` attribute, and
341
+ decided against because the additional ``raise ... `` statement may be an
342
+ important clue when understanding some error. If end users wish to pop a
343
+ frame from the traceback, they can do with a separate context manager.
344
+
345
+
332
346
Footnotes
333
347
=========
334
348
@@ -337,7 +351,7 @@ Footnotes
337
351
<https://github.com/search?q=%2Ffor+%5Cw%2B+in+%5Beg%5D%5Cw*%5C.exceptions%3A%2F+language%3APython&type=code> `__
338
352
for ``for \w+ in [eg]\w*\.exceptions: ``, we find:
339
353
340
- * Four functions implementing ``flat_exceptions () `` semantics, none of
354
+ * Four functions implementing ``leaf_exceptions () `` semantics, none of
341
355
which preserve tracebacks:
342
356
(`one <https://github.com/nonebot/nonebot2/blob/570bd9587af99dd01a7d5421d3105d8a8e2aba32/nonebot/utils.py#L259-L266 >`__,
343
357
`two <https://github.com/HypothesisWorks/hypothesis/blob/7c49f2daf602bc4e51161b6c0bc21720d64de9eb/hypothesis-python/src/hypothesis/core.py#L763-L770 >`__,
@@ -354,7 +368,7 @@ Footnotes
354
368
`six <https://github.com/sobolevn/faststream/blob/0d6c9ee6b7703efab04387c51c72876e25ad91a7/faststream/app.py#L54-L56 >`__)
355
369
356
370
* Seven cases which mishandle nested exception groups, and would thus
357
- benefit from ``flat_exceptions () ``. We were surprised to note that only
371
+ benefit from ``leaf_exceptions () ``. We were surprised to note that only
358
372
one of these cases could straightforwardly be replaced by use of an
359
373
``except* `` clause or ``.subgroup() `` method.
360
374
(`one <https://github.com/vertexproject/synapse/blob/ed8148abb857d4445d727768d4c57f4f11b0d20a/synapse/lib/stormlib/iters.py#L82-L88 >`__,
@@ -375,6 +389,14 @@ Footnotes
375
389
We expect that ``except* `` will be widely used in such cases by the time
376
390
that the methods proposed by this PEP are widely available.
377
391
392
+ .. [#naming ]
393
+ The name ``leaf_exceptions() `` was `first proposed `__ in an early
394
+ precursor to :pep: `654 `. If the prototype had matched ``except* ``
395
+ in wrapping bare exceptions in a group, we might even have included
396
+ a ``.leaf_exceptions() `` method in that earlier PEP!
397
+
398
+ __ https://github.com/python-trio/exceptiongroup/pull/13
399
+
378
400
379
401
Copyright
380
402
=========
0 commit comments