@@ -17,7 +17,7 @@ As :pep:`654` :class:`ExceptionGroup` has come into widespread use across the
1717Python community, some common but awkward patterns have emerged. We therefore
1818propose adding two new methods to exception objects:
1919
20- - :meth: `!BaseExceptionGroup.flat_exceptions `, returning the 'leaf' exceptions as
20+ - :meth: `!BaseExceptionGroup.leaf_exceptions `, returning the 'leaf' exceptions as
2121 a list, with each traceback composited from any intermediate groups.
2222
2323- :meth: `!BaseException.preserve_context `, a context manager which
@@ -39,10 +39,10 @@ often write code to process or respond to individual leaf exceptions, for
3939example when implementing middleware, error logging, or response handlers in
4040a web framework.
4141
42- `Searching GitHub `__ found four implementations of :meth: `!flat_exceptions ` by
42+ `Searching GitHub `__ found four implementations of :meth: `!leaf_exceptions ` by
4343various names in the first sixty hits, of which none handle
4444tracebacks.\ [#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
4646a method on the :class: `BaseException ` type with proper traceback preservation
4747will improve error-handling and debugging experiences across the ecosystem.
4848
@@ -55,7 +55,7 @@ unwrap ``HTTPException`` if that is the sole leaf of a group:
5555.. code-block :: python
5656
5757 except * HTTPException as group:
58- first, * rest = group.flat_exceptions () # get the whole traceback :-)
58+ first, * rest = group.leaf_exceptions () # get the whole traceback :-)
5959 if not rest:
6060 raise first
6161 raise
@@ -78,12 +78,12 @@ readable, and easy-to-use solution for these cases.
7878Specification
7979=============
8080
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
8282following signature:
8383
8484.. code-block :: python
8585
86- def flat_exceptions (self , * , fix_tracebacks = True ) -> list[BaseException ]:
86+ def leaf_exceptions (self , * , fix_tracebacks = True ) -> list[BaseException ]:
8787 """
8888 Return a flat list of all 'leaf' exceptions in the group.
8989
@@ -118,7 +118,7 @@ Usage example:
118118 try :
119119 user_code_here()
120120 except * HTTPException as group:
121- first, * rest = group.flat_exceptions ()
121+ first, * rest = group.leaf_exceptions ()
122122 if rest:
123123 raise # handled by internal-server-error middleware
124124 ... # logging, cache updates, etc.
@@ -142,12 +142,12 @@ Backwards Compatibility
142142
143143Adding new methods to built-in classes, especially those as widely used as
144144``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
146146`three unrelated hits `__ respectively). If user-defined methods with these
147147names exist in private code they will shadow those proposed in the PEP,
148148without changing runtime behavior.
149149
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
151151__ https://github.com/search?q=%2F%5C.preserve_context%5C%28%2F+language%3APython&type=code
152152
153153
@@ -157,20 +157,20 @@ How to Teach This
157157Working with exception groups is an intermediate-to-advanced topic, unlikely
158158to arise for beginning programmers. We therefore suggest teaching this topic
159159via 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
161161with the ``.split() `` and ``.subgroup() `` methods, and mentioning
162162``.preserve_context() `` as an advanced option to address specific pain points.
163163
164164Both the API reference and the existing `ExceptionGroup tutorial `__
165165should 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
167167``.preserve_context() `` help simplify error handling logic. Downstream
168168libraries which often use exception groups could include similar docs.
169169
170170__ https://docs.python.org/3/tutorial/errors.html#raising-and-handling-multiple-unrelated-exceptions
171171
172172We 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 ``
174174or re-raising a leaf exception, and suggest using ``.preserve_context() `` when
175175re-raising a leaf exception inside an ``except* `` block would override any
176176existing context.
@@ -186,7 +186,7 @@ on older versions of Python, and can demonstrate the intended semantics.
186186We have found these helper functions quite useful when working with
187187:class: `ExceptionGroup `\ s in a large production codebase.
188188
189- A ``flat_exceptions () `` helper function
189+ A ``leaf_exceptions () `` helper function
190190---------------------------------------
191191
192192.. code-block :: python
@@ -196,7 +196,7 @@ A ``flat_exceptions()`` helper function
196196 from types import TracebackType
197197
198198
199- def flat_exceptions (
199+ def leaf_exceptions (
200200 self : BaseExceptionGroup, * , fix_traceback : bool = True
201201 ) -> list[BaseException ]:
202202 """
@@ -297,11 +297,11 @@ Add ``BaseException.as_group()`` (or group methods)
297297Our survey of ``ExceptionGroup ``-related error handling code also observed
298298many cases of duplicated logic to handle both a bare exception, and the same
299299kind of exception inside a group (often incorrectly, motivating
300- ``.flat_exceptions () ``).
300+ ``.leaf_exceptions () ``).
301301
302302We briefly `proposed <https://github.com/python/cpython/issues/125825 >`__
303303adding ``.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.
305305As a cleaner alternative, we sketched out an ``.as_group() `` method:
306306
307307.. code-block :: python
@@ -329,6 +329,20 @@ less magical and tempting to use in cases where it would not be appropriate.
329329We could be argued around though, if others prefer this form.
330330
331331
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+
332346Footnotes
333347=========
334348
@@ -337,7 +351,7 @@ Footnotes
337351 <https://github.com/search?q=%2Ffor+%5Cw%2B+in+%5Beg%5D%5Cw*%5C.exceptions%3A%2F+language%3APython&type=code> `__
338352 for ``for \w+ in [eg]\w*\.exceptions: ``, we find:
339353
340- * Four functions implementing ``flat_exceptions () `` semantics, none of
354+ * Four functions implementing ``leaf_exceptions () `` semantics, none of
341355 which preserve tracebacks:
342356 (`one <https://github.com/nonebot/nonebot2/blob/570bd9587af99dd01a7d5421d3105d8a8e2aba32/nonebot/utils.py#L259-L266 >`__,
343357 `two <https://github.com/HypothesisWorks/hypothesis/blob/7c49f2daf602bc4e51161b6c0bc21720d64de9eb/hypothesis-python/src/hypothesis/core.py#L763-L770 >`__,
@@ -354,7 +368,7 @@ Footnotes
354368 `six <https://github.com/sobolevn/faststream/blob/0d6c9ee6b7703efab04387c51c72876e25ad91a7/faststream/app.py#L54-L56 >`__)
355369
356370 * 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
358372 one of these cases could straightforwardly be replaced by use of an
359373 ``except* `` clause or ``.subgroup() `` method.
360374 (`one <https://github.com/vertexproject/synapse/blob/ed8148abb857d4445d727768d4c57f4f11b0d20a/synapse/lib/stormlib/iters.py#L82-L88 >`__,
@@ -375,6 +389,14 @@ Footnotes
375389 We expect that ``except* `` will be widely used in such cases by the time
376390 that the methods proposed by this PEP are widely available.
377391
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+
378400
379401Copyright
380402=========
0 commit comments