gh-135629: rewrite language reference section on except* to improve clarity (#136150)

This commit is contained in:
Irit Katriel 2025-09-16 13:56:51 +01:00 committed by GitHub
parent 01cc53295c
commit a651ec9524
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -335,15 +335,29 @@ stored in the :mod:`sys` module is reset to its previous value::
:keyword:`!except*` clause :keyword:`!except*` clause
-------------------------- --------------------------
The :keyword:`!except*` clause(s) are used for handling The :keyword:`!except*` clause(s) specify one or more handlers for groups of
:exc:`ExceptionGroup`\s. The exception type for matching is interpreted as in exceptions (:exc:`BaseExceptionGroup` instances). A :keyword:`try` statement
the case of :keyword:`except`, but in the case of exception groups we can have can have either :keyword:`except` or :keyword:`!except*` clauses, but not both.
partial matches when the type matches some of the exceptions in the group. The exception type for matching is mandatory in the case of :keyword:`!except*`,
This means that multiple :keyword:`!except*` clauses can execute, so ``except*:`` is a syntax error. The type is interpreted as in the case of
each handling part of the exception group. :keyword:`!except`, but matching is performed on the exceptions contained in the
Each clause executes at most once and handles an exception group group that is being handled. An :exc:`TypeError` is raised if a matching
of all matching exceptions. Each exception in the group is handled by at most type is a subclass of :exc:`!BaseExceptionGroup`, because that would have
one :keyword:`!except*` clause, the first that matches it. :: ambiguous semantics.
When an exception group is raised in the try block, each :keyword:`!except*`
clause splits (see :meth:`~BaseExceptionGroup.split`) it into the subgroups
of matching and non-matching exceptions. If the matching subgroup is not empty,
it becomes the handled exception (the value returned from :func:`sys.exception`)
and assigned to the target of the :keyword:`!except*` clause (if there is one).
Then, the body of the :keyword:`!except*` clause executes. If the non-matching
subgroup is not empty, it is processed by the next :keyword:`!except*` in the
same manner. This continues until all exceptions in the group have been matched,
or the last :keyword:`!except*` clause has run.
After all :keyword:`!except*` clauses execute, the group of unhandled exceptions
is merged with any exceptions that were raised or re-raised from within
:keyword:`!except*` clauses. This merged exception group propagates on.::
>>> try: >>> try:
... raise ExceptionGroup("eg", ... raise ExceptionGroup("eg",
@ -356,22 +370,18 @@ one :keyword:`!except*` clause, the first that matches it. ::
caught <class 'ExceptionGroup'> with nested (TypeError(2),) caught <class 'ExceptionGroup'> with nested (TypeError(2),)
caught <class 'ExceptionGroup'> with nested (OSError(3), OSError(4)) caught <class 'ExceptionGroup'> with nested (OSError(3), OSError(4))
+ Exception Group Traceback (most recent call last): + Exception Group Traceback (most recent call last):
| File "<stdin>", line 2, in <module> | File "<doctest default[0]>", line 2, in <module>
| ExceptionGroup: eg | raise ExceptionGroup("eg",
| [ValueError(1), TypeError(2), OSError(3), OSError(4)])
| ExceptionGroup: eg (1 sub-exception)
+-+---------------- 1 ---------------- +-+---------------- 1 ----------------
| ValueError: 1 | ValueError: 1
+------------------------------------ +------------------------------------
If the exception raised from the :keyword:`try` block is not an exception group
Any remaining exceptions that were not handled by any :keyword:`!except*` and its type matches one of the :keyword:`!except*` clauses, it is caught and
clause are re-raised at the end, along with all exceptions that were wrapped by an exception group with an empty message string. This ensures that the
raised from within the :keyword:`!except*` clauses. If this list contains type of the target ``e`` is consistently :exc:`BaseExceptionGroup`::
more than one exception to reraise, they are combined into an exception
group.
If the raised exception is not an exception group and its type matches
one of the :keyword:`!except*` clauses, it is caught and wrapped by an
exception group with an empty message string. ::
>>> try: >>> try:
... raise BlockingIOError ... raise BlockingIOError
@ -380,13 +390,7 @@ exception group with an empty message string. ::
... ...
ExceptionGroup('', (BlockingIOError())) ExceptionGroup('', (BlockingIOError()))
An :keyword:`!except*` clause must have a matching expression; it cannot be ``except*:``. :keyword:`break`, :keyword:`continue` and :keyword:`return`
Furthermore, this expression cannot contain exception group types, because that would
have ambiguous semantics.
It is not possible to mix :keyword:`except` and :keyword:`!except*`
in the same :keyword:`try`.
The :keyword:`break`, :keyword:`continue`, and :keyword:`return` statements
cannot appear in an :keyword:`!except*` clause. cannot appear in an :keyword:`!except*` clause.