mirror of https://github.com/python/cpython
bpo-45292: [PEP-654] exception groups and except* documentation (GH-30158)
This commit is contained in:
parent
68c76d9766
commit
9925e70e48
|
@ -851,6 +851,78 @@ The following exceptions are used as warning categories; see the
|
|||
.. versionadded:: 3.2
|
||||
|
||||
|
||||
Exception groups
|
||||
----------------
|
||||
|
||||
The following are used when it is necessary to raise multiple unrelated
|
||||
exceptions. They are part of the exception hierarchy so they can be
|
||||
handled with :keyword:`except` like all other exceptions. In addition,
|
||||
they are recognised by :keyword:`except*<except_star>`, which matches
|
||||
their subgroups based on the types of the contained exceptions.
|
||||
|
||||
.. exception:: ExceptionGroup(msg, excs)
|
||||
.. exception:: BaseExceptionGroup(msg, excs)
|
||||
|
||||
Both of these exception types wrap the exceptions in the sequence ``excs``.
|
||||
The ``msg`` parameter must be a string. The difference between the two
|
||||
classes is that :exc:`BaseExceptionGroup` extends :exc:`BaseException` and
|
||||
it can wrap any exception, while :exc:`ExceptionGroup` extends :exc:`Exception`
|
||||
and it can only wrap subclasses of :exc:`Exception`. This design is so that
|
||||
``except Exception`` catches an :exc:`ExceptionGroup` but not
|
||||
:exc:`BaseExceptionGroup`.
|
||||
|
||||
The :exc:`BaseExceptionGroup` constructor returns an :exc:`ExceptionGroup`
|
||||
rather than a :exc:`BaseExceptionGroup` if all contained exceptions are
|
||||
:exc:`Exception` instances, so it can be used to make the selection
|
||||
automatic. The :exc:`ExceptionGroup` constructor, on the other hand,
|
||||
raises a :exc:`TypeError` if any contained exception is not an
|
||||
:exc:`Exception` subclass.
|
||||
|
||||
.. method:: subgroup(condition)
|
||||
|
||||
Returns an exception group that contains only the exceptions from the
|
||||
current group that match *condition*, or ``None`` if the result is empty.
|
||||
|
||||
The condition can be either a function that accepts an exception and returns
|
||||
true for those that should be in the subgroup, or it can be an exception type
|
||||
or a tuple of exception types, which is used to check for a match using the
|
||||
same check that is used in an ``except`` clause.
|
||||
|
||||
The nesting structure of the current exception is preserved in the result,
|
||||
as are the values of its :attr:`message`, :attr:`__traceback__`,
|
||||
:attr:`__cause__`, :attr:`__context__` and :attr:`__note__` fields.
|
||||
Empty nested groups are omitted from the result.
|
||||
|
||||
The condition is checked for all exceptions in the nested exception group,
|
||||
including the top-level and any nested exception groups. If the condition is
|
||||
true for such an exception group, it is included in the result in full.
|
||||
|
||||
.. method:: split(condition)
|
||||
|
||||
Like :meth:`subgroup`, but returns the pair ``(match, rest)`` where ``match``
|
||||
is ``subgroup(condition)`` and ``rest`` is the remaining non-matching
|
||||
part.
|
||||
|
||||
.. method:: derive(excs)
|
||||
|
||||
Returns an exception group with the same :attr:`message`,
|
||||
:attr:`__traceback__`, :attr:`__cause__`, :attr:`__context__`
|
||||
and :attr:`__note__` but which wraps the exceptions in ``excs``.
|
||||
|
||||
This method is used by :meth:`subgroup` and :meth:`split`. A
|
||||
subclass needs to override it in order to make :meth:`subgroup`
|
||||
and :meth:`split` return instances of the subclass rather
|
||||
than :exc:`ExceptionGroup`. ::
|
||||
|
||||
>>> class MyGroup(ExceptionGroup):
|
||||
... def derive(self, exc):
|
||||
... return MyGroup(self.message, exc)
|
||||
...
|
||||
>>> MyGroup("eg", [ValueError(1), TypeError(2)]).split(TypeError)
|
||||
(MyGroup('eg', [TypeError(2)]), MyGroup('eg', [ValueError(1)]))
|
||||
|
||||
.. versionadded:: 3.11
|
||||
|
||||
|
||||
Exception hierarchy
|
||||
-------------------
|
||||
|
|
|
@ -199,6 +199,7 @@ returns the list ``[0, 1, 2]``.
|
|||
|
||||
.. _try:
|
||||
.. _except:
|
||||
.. _except_star:
|
||||
.. _finally:
|
||||
|
||||
The :keyword:`!try` statement
|
||||
|
@ -216,12 +217,16 @@ The :keyword:`try` statement specifies exception handlers and/or cleanup code
|
|||
for a group of statements:
|
||||
|
||||
.. productionlist:: python-grammar
|
||||
try_stmt: `try1_stmt` | `try2_stmt`
|
||||
try_stmt: `try1_stmt` | `try2_stmt` | `try3_stmt`
|
||||
try1_stmt: "try" ":" `suite`
|
||||
: ("except" [`expression` ["as" `identifier`]] ":" `suite`)+
|
||||
: ["else" ":" `suite`]
|
||||
: ["finally" ":" `suite`]
|
||||
try2_stmt: "try" ":" `suite`
|
||||
: ("except" "*" `expression` ["as" `identifier`] ":" `suite`)+
|
||||
: ["else" ":" `suite`]
|
||||
: ["finally" ":" `suite`]
|
||||
try3_stmt: "try" ":" `suite`
|
||||
: "finally" ":" `suite`
|
||||
|
||||
|
||||
|
@ -304,6 +309,47 @@ when leaving an exception handler::
|
|||
>>> print(sys.exc_info())
|
||||
(None, None, None)
|
||||
|
||||
.. index::
|
||||
keyword: except_star
|
||||
|
||||
The :keyword:`except*<except_star>` clause(s) are used for handling
|
||||
:exc:`ExceptionGroup`s. The exception type for matching is interpreted as in
|
||||
the case of :keyword:`except`, but in the case of exception groups we can have
|
||||
partial matches when the type matches some of the exceptions in the group.
|
||||
This means that multiple except* clauses can execute, each handling part of
|
||||
the exception group. Each clause executes once and handles an exception group
|
||||
of all matching exceptions. Each exception in the group is handled by at most
|
||||
one except* clause, the first that matches it. ::
|
||||
|
||||
>>> try:
|
||||
... raise ExceptionGroup("eg",
|
||||
... [ValueError(1), TypeError(2), OSError(3), OSError(4)])
|
||||
... except* TypeError as e:
|
||||
... print(f'caught {type(e)} with nested {e.exceptions}')
|
||||
... except* OSError as e:
|
||||
... print(f'caught {type(e)} with nested {e.exceptions}')
|
||||
...
|
||||
caught <class 'ExceptionGroup'> with nested (TypeError(2),)
|
||||
caught <class 'ExceptionGroup'> with nested (OSError(3), OSError(4))
|
||||
+ Exception Group Traceback (most recent call last):
|
||||
| File "<stdin>", line 2, in <module>
|
||||
| ExceptionGroup: eg
|
||||
+-+---------------- 1 ----------------
|
||||
| ValueError: 1
|
||||
+------------------------------------
|
||||
>>>
|
||||
|
||||
Any remaining exceptions that were not handled by any except* clause
|
||||
are re-raised at the end, combined into an exception group along with
|
||||
all exceptions that were raised from within except* clauses.
|
||||
|
||||
An except* clause must have a matching type, and this type cannot be a
|
||||
subclass of :exc:`BaseExceptionGroup`. It is not possible to mix except
|
||||
and except* in the same :keyword:`try`. :keyword:`break`,
|
||||
:keyword:`continue` and :keyword:`return` cannot appear in an except*
|
||||
clause.
|
||||
|
||||
|
||||
.. index::
|
||||
keyword: else
|
||||
statement: return
|
||||
|
|
|
@ -462,3 +462,92 @@ used in a way that ensures they are always cleaned up promptly and correctly. ::
|
|||
After the statement is executed, the file *f* is always closed, even if a
|
||||
problem was encountered while processing the lines. Objects which, like files,
|
||||
provide predefined clean-up actions will indicate this in their documentation.
|
||||
|
||||
|
||||
.. _tut-exception-groups:
|
||||
|
||||
Raising and Handling Multiple Unrelated Exceptions
|
||||
==================================================
|
||||
|
||||
There are situations where it is necessary to report several exceptions that
|
||||
have occurred. This it often the case in concurrency frameworks, when several
|
||||
tasks may have failed in parallel, but there are also other use cases where
|
||||
it is desirable to continue execution and collect multiple errors rather than
|
||||
raise the first exception.
|
||||
|
||||
The builtin :exc:`ExceptionGroup` wraps a list of exception instances so
|
||||
that they can be raised together. It is an exception itself, so it can be
|
||||
caught like any other exception. ::
|
||||
|
||||
>>> def f():
|
||||
... excs = [OSError('error 1'), SystemError('error 2')]
|
||||
... raise ExceptionGroup('there were problems', excs)
|
||||
...
|
||||
>>> f()
|
||||
+ Exception Group Traceback (most recent call last):
|
||||
| File "<stdin>", line 1, in <module>
|
||||
| File "<stdin>", line 3, in f
|
||||
| ExceptionGroup: there were problems
|
||||
+-+---------------- 1 ----------------
|
||||
| OSError: error 1
|
||||
+---------------- 2 ----------------
|
||||
| SystemError: error 2
|
||||
+------------------------------------
|
||||
>>> try:
|
||||
... f()
|
||||
... except Exception as e:
|
||||
... print(f'caught {type(e)}: e')
|
||||
...
|
||||
caught <class 'ExceptionGroup'>: e
|
||||
>>>
|
||||
|
||||
By using ``except*`` instead of ``except``, we can selectively
|
||||
handle only the exceptions in the group that match a certain
|
||||
type. In the following example, which shows a nested exception
|
||||
group, each ``except*`` clause extracts from the group exceptions
|
||||
of a certain type while letting all other exceptions propagate to
|
||||
other clauses and eventually to be reraised. ::
|
||||
|
||||
>>> def f():
|
||||
... raise ExceptionGroup("group1",
|
||||
... [OSError(1),
|
||||
... SystemError(2),
|
||||
... ExceptionGroup("group2",
|
||||
... [OSError(3), RecursionError(4)])])
|
||||
...
|
||||
>>> try:
|
||||
... f()
|
||||
... except* OSError as e:
|
||||
... print("There were OSErrors")
|
||||
... except* SystemError as e:
|
||||
... print("There were SystemErrors")
|
||||
...
|
||||
There were OSErrors
|
||||
There were SystemErrors
|
||||
+ Exception Group Traceback (most recent call last):
|
||||
| File "<stdin>", line 2, in <module>
|
||||
| File "<stdin>", line 2, in f
|
||||
| ExceptionGroup: group1
|
||||
+-+---------------- 1 ----------------
|
||||
| ExceptionGroup: group2
|
||||
+-+---------------- 1 ----------------
|
||||
| RecursionError: 4
|
||||
+------------------------------------
|
||||
>>>
|
||||
|
||||
Note that the exceptions nested in an exception group must be instances,
|
||||
not types. This is because in practice the exceptions would typically
|
||||
be ones that have already been raised and caught by the program, along
|
||||
the following pattern::
|
||||
|
||||
>>> excs = []
|
||||
... for test in tests:
|
||||
... try:
|
||||
... test.run()
|
||||
... except Exception as e:
|
||||
... excs.append(e)
|
||||
...
|
||||
>>> if excs:
|
||||
... raise ExceptionGroup("Test Failures", excs)
|
||||
...
|
||||
|
||||
|
|
Loading…
Reference in New Issue