Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add section about handling ExceptionGroups #1841

Merged
merged 2 commits into from
Feb 26, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 55 additions & 12 deletions pep-0654.rst
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,47 @@ in the following example:
ValueError: 1
>>>

Handling ``ExceptionGroups``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

We expect that when programs catch and handle ``ExceptionGroups``, they will
typically either query to check if it has leaf exceptions for which some
condition holds (using ``subgroup`` or ``split``) or format the exception
(using the ``traceback`` module's methods).

It is unlikely to be useful to inspect the individual leaf exceptions. To see
why, suppose that an application caught an ``ExceptionGroup`` raised in an
``asyncio.gather()`` call. At this stage, the context for each specific
exception is lost. Any recovery for this exception should have been performed
before it was grouped with other exceptions into the ``ExceptionGroup`` [10]_.
Furthermore, the application is likely to react in the same way to any number
of instances of a certain exception type, so it is more likely that we will
want to know whether ``eg.subgroup(T)`` is None or not, than we are to be
intersted in the number of ``Ts`` in ``eg``.

If it does turn out to be necessary for an applicaiton to iterate over the
individual exceptions of an ``ExceptionGroup`` ``eg``, this can be done by
calling ``traverse(eg)``, where ``traverse`` is defined as follows:

.. code-block::

def traverse(exc, tbs=None, cause=None, context=None):
if tbs is None:
tbs = []
cause = exc.__cause__
context = exc.__context__

tbs.append(exc.__traceback__)
if isinstance(exc, ExceptionGroup):
for e in exc.errors:
traverse(e, tbs, cause, context)
else:
# exc is a leaf exception and its traceback
# is the concatenation of the traceback in tbs
process_leaf(exc, tbs, cause, context)
tbs.pop()


except*
-------

Expand Down Expand Up @@ -930,7 +971,7 @@ Reference Implementation
========================

We developed these concepts (and the examples for this PEP) with
the help of the reference implementation [10]_.
the help of the reference implementation [11]_.

It has the builtin ``ExceptionGroup`` along with the changes to the traceback
formatting code, in addition to the grammar, compiler and interpreter changes
Expand All @@ -951,18 +992,20 @@ metadata as the original, while the raised ones do not.
Rejected Ideas
==============

The ExceptionGroup API
----------------------
Make ExceptionGroup Iterable
----------------------------

We considered making ``ExceptionGroups`` iterable, so that ``list(eg)`` would
produce a flattened list of the leaf exceptions contained in the group.
We decided that this would not be not be a sound API, because the metadata
We decided that this would not be a sound API, because the metadata
(cause, context and traceback) of the individual exceptions in a group is
incomplete and this could create problems. If use cases arise where this
can be helpful, we can document (or even provide in the standard library)
a sound recipe for accessing an individual exception: use the ``split()``
method to create an ``ExceptionGroup`` for a single exception and then
extract the contained exception with the correct metadata.
incomplete and this could create problems.

Furthermore, as we explained in the `Handling ExceptionGroups`_ section, we
find it unlikely that iteration over leaf exceptions will have many use cases.
We did, however, provide there the code for a traversal algorithm that
correctly constructs each leaf exception's metadata. If it does turn out to
be useful in practice, we can add that utility to the standard library.

Traceback Representation
------------------------
Expand Down Expand Up @@ -1133,7 +1176,7 @@ See Also
========

* An analysis of how exception groups will likely be used in asyncio
programs: [11]_.
programs: [10]_.

* The issue where the ``except*`` concept was first formalized: [12]_.

Expand Down Expand Up @@ -1163,9 +1206,9 @@ References

.. [9] https://trio.readthedocs.io/en/stable/reference-core.html#trio.MultiError

.. [10] https://github.com/iritkatriel/cpython/tree/exceptionGroup-stage5
.. [10] https://github.com/python/exceptiongroups/issues/3#issuecomment-716203284

.. [11] https://github.com/python/exceptiongroups/issues/3#issuecomment-716203284
.. [11] https://github.com/iritkatriel/cpython/tree/exceptionGroup-stage5

.. [12] https://github.com/python/exceptiongroups/issues/4

Expand Down