PEP-654: Add example of subgroup callback with side effects (#1852)
This commit is contained in:
parent
f69d426700
commit
274670b330
98
pep-0654.rst
98
pep-0654.rst
|
@ -335,9 +335,9 @@ typically either query to check if it has leaf exceptions for which some
|
||||||
condition holds (using ``subgroup`` or ``split``) or format the exception
|
condition holds (using ``subgroup`` or ``split``) or format the exception
|
||||||
(using the ``traceback`` module's methods).
|
(using the ``traceback`` module's methods).
|
||||||
|
|
||||||
It is unlikely to be useful to inspect the individual leaf exceptions. To see
|
It is less likely to be useful to iterate over the individual leaf exceptions.
|
||||||
why, suppose that an application caught an ``ExceptionGroup`` raised in an
|
To see why, suppose that an application caught an ``ExceptionGroup`` raised in
|
||||||
``asyncio.gather()`` call. At this stage, the context for each specific
|
an ``asyncio.gather()`` call. At this stage, the context for each specific
|
||||||
exception is lost. Any recovery for this exception should have been performed
|
exception is lost. Any recovery for this exception should have been performed
|
||||||
before it was grouped with other exceptions into the ``ExceptionGroup`` [10]_.
|
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
|
Furthermore, the application is likely to react in the same way to any number
|
||||||
|
@ -345,27 +345,101 @@ 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
|
want to know whether ``eg.subgroup(T)`` is None or not, than we are to be
|
||||||
interested in the number of ``Ts`` in ``eg``.
|
interested in the number of ``Ts`` in ``eg``.
|
||||||
|
|
||||||
If it does turn out to be necessary for an application to iterate over the
|
However, there are situations where it is necessary to inspect the
|
||||||
individual exceptions of an ``ExceptionGroup`` ``eg``, this can be done by
|
individual leaf exceptions. For example, suppose that we have an
|
||||||
calling ``traverse(eg)``, where ``traverse`` is defined as follows:
|
``ExceptionGroup`` ``eg`` and we want to log the ``OSErrors`` that have a
|
||||||
|
specific error code and reraise everything else. We can do this by passing
|
||||||
|
a function with side effects to ``subgroup``, as follows:
|
||||||
|
|
||||||
.. code-block::
|
.. code-block::
|
||||||
|
|
||||||
def traverse(exc, tbs=None):
|
def log_and_ignore_ENOENT(err):
|
||||||
|
if isinstance(err, OSError) and err.errno == ENOENT:
|
||||||
|
log(err)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
. . .
|
||||||
|
except ExceptionGroup as eg:
|
||||||
|
eg = eg.subgroup(log_and_ignore_ENOENT)
|
||||||
|
if eg is not None:
|
||||||
|
raise eg
|
||||||
|
|
||||||
|
|
||||||
|
In the previous example, when ``log_and_ignore_ENOENT`` is invoked on a leaf
|
||||||
|
exception, only part of this exception's traceback is accessible -- the part
|
||||||
|
referenced from its ``__traceback__`` field. If we need the full traceback,
|
||||||
|
we need to look at the concatenation of the tracebacks of the exceptions on
|
||||||
|
the path from the root to this leaf. We can get that with direct iteration,
|
||||||
|
recursively, as follows:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
def leaf_generator(exc, tbs=None):
|
||||||
if tbs is None:
|
if tbs is None:
|
||||||
tbs = []
|
tbs = []
|
||||||
|
|
||||||
tbs.append(exc.__traceback__)
|
tbs.append(exc.__traceback__)
|
||||||
if isinstance(exc, ExceptionGroup):
|
if isinstance(exc, ExceptionGroup):
|
||||||
for e in exc.errors:
|
for e in exc.errors:
|
||||||
traverse(e, tbs)
|
yield from leaf_generator(e, tbs)
|
||||||
else:
|
else:
|
||||||
# exc is a leaf exception and its traceback
|
# exc is a leaf exception and its traceback
|
||||||
# is the concatenation of the traceback in tbs
|
# is the concatenation of the traceback
|
||||||
process_leaf(exc, tbs)
|
# segments in tbs
|
||||||
|
yield exc, tbs
|
||||||
tbs.pop()
|
tbs.pop()
|
||||||
|
|
||||||
|
|
||||||
|
We can then process the full tracebacks of the leaf exceptions:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
>>> import traceback
|
||||||
|
>>>
|
||||||
|
>>> def g(v):
|
||||||
|
... try:
|
||||||
|
... raise ValueError(v)
|
||||||
|
... except Exception as e:
|
||||||
|
... return e
|
||||||
|
...
|
||||||
|
>>> def f():
|
||||||
|
... raise ExceptionGroup("eg", [g(1), g(2)])
|
||||||
|
...
|
||||||
|
>>> try:
|
||||||
|
... f()
|
||||||
|
... except BaseException as e:
|
||||||
|
... eg = e
|
||||||
|
...
|
||||||
|
>>> for (i, (exc, tbs)) in enumerate(leaf_generator(eg)):
|
||||||
|
... print(f"\n>>> Exception #{i+1}:")
|
||||||
|
... traceback.print_exception(exc)
|
||||||
|
... print(f"The complete traceback for Exception #{i+1}:")
|
||||||
|
... for tb in tbs:
|
||||||
|
... traceback.print_tb(tb)
|
||||||
|
...
|
||||||
|
|
||||||
|
>>> Exception #1:
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "<stdin>", line 3, in g
|
||||||
|
ValueError: 1
|
||||||
|
The complete traceback for Exception #1
|
||||||
|
File "<stdin>", line 2, in <module>
|
||||||
|
File "<stdin>", line 2, in f
|
||||||
|
File "<stdin>", line 3, in g
|
||||||
|
|
||||||
|
>>> Exception #2:
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "<stdin>", line 3, in g
|
||||||
|
ValueError: 2
|
||||||
|
The complete traceback for Exception #2:
|
||||||
|
File "<stdin>", line 2, in <module>
|
||||||
|
File "<stdin>", line 2, in f
|
||||||
|
File "<stdin>", line 3, in g
|
||||||
|
>>>
|
||||||
|
|
||||||
except*
|
except*
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
@ -416,7 +490,7 @@ Exceptions are matched using a subclass check. For example:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
low_level_os_operation()
|
low_level_os_operation()
|
||||||
except *OSerror as eg:
|
except *OSError as eg:
|
||||||
for e in eg.errors:
|
for e in eg.errors:
|
||||||
print(type(e).__name__)
|
print(type(e).__name__)
|
||||||
|
|
||||||
|
@ -864,7 +938,7 @@ while letting all other exceptions propagate.
|
||||||
|
|
||||||
try:
|
try:
|
||||||
low_level_os_operation()
|
low_level_os_operation()
|
||||||
except *OSerror as errors:
|
except *OSError as errors:
|
||||||
raise errors.subgroup(lambda e: e.errno != errno.EPIPE) from None
|
raise errors.subgroup(lambda e: e.errno != errno.EPIPE) from None
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue