From 7cb00257dccf67c25e8d2497b34f0f3ffe070508 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Mon, 30 Jun 2025 23:11:10 +0100 Subject: [PATCH 1/3] gh-135629: rewrite language reference section on except* to improve clarity --- Doc/reference/compound_stmts.rst | 42 +++++++++++++++++--------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst index e95fa3a6424e23..9fc16e81126511 100644 --- a/Doc/reference/compound_stmts.rst +++ b/Doc/reference/compound_stmts.rst @@ -335,15 +335,25 @@ stored in the :mod:`sys` module is reset to its previous value:: :keyword:`!except*` clause -------------------------- -The :keyword:`!except*` 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 :keyword:`!except*` clauses can execute, -each handling part of the exception group. -Each clause executes at most once and handles an exception group -of all matching exceptions. Each exception in the group is handled by at most -one :keyword:`!except*` clause, the first that matches it. :: +The :keyword:`!except*` clause(s) specify one or more handlers for groups of +exceptions (:exc:`BaseExceptionGroup` instances). A :keyword:`try` statement +can have either :keyword:`except` or :keyword:`!except*` clauses, but not both. +The exception type for matching is interpreted as in the case of +:keyword:`except`, but matching is performed on the exceptions contained in the +group that is being handled. Each :keyword:`!except*` clause splits (see +:meth:`~BaseExceptionGroup.split`) the exception group 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 `sys.exception()`) and +assigned to the target of the :keyword:`!except*` clause (if there is one). Then, +the body of the :keyword:`!except*` clause is executed. 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 the last :keyword:`!except*` runs, and before the :keyword:`!finally` block +(if there is one) executes, 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: ... raise ExceptionGroup("eg", @@ -362,16 +372,10 @@ one :keyword:`!except*` clause, the first that matches it. :: | ValueError: 1 +------------------------------------ - -Any remaining exceptions that were not handled by any :keyword:`!except*` -clause are re-raised at the end, along with all exceptions that were -raised from within the :keyword:`!except*` clauses. If this list contains -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. :: +If the exception raised from the :keyword:`try` block 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. This ensures that the +type of the target `e` is consistently :exc:`BaseExceptionGroup`:: >>> try: ... raise BlockingIOError From b9cb40a93c034bb24149c0d95a8d3fc55fd5ec47 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Mon, 30 Jun 2025 23:17:34 +0100 Subject: [PATCH 2/3] backticks --- Doc/reference/compound_stmts.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst index 9fc16e81126511..f6d2f21e0d2511 100644 --- a/Doc/reference/compound_stmts.rst +++ b/Doc/reference/compound_stmts.rst @@ -343,7 +343,7 @@ The exception type for matching is interpreted as in the case of group that is being handled. Each :keyword:`!except*` clause splits (see :meth:`~BaseExceptionGroup.split`) the exception group 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 `sys.exception()`) and +becomes the handled exception (the value returned from ``sys.exception()``) and assigned to the target of the :keyword:`!except*` clause (if there is one). Then, the body of the :keyword:`!except*` clause is executed. If the non-matching subgroup is not empty, it is processed by the next :keyword:`!except*` in the @@ -375,7 +375,7 @@ clauses. This merged exception group propagates on. If the exception raised from the :keyword:`try` block 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. This ensures that the -type of the target `e` is consistently :exc:`BaseExceptionGroup`:: +type of the target ``e`` is consistently :exc:`BaseExceptionGroup`:: >>> try: ... raise BlockingIOError From fda1d972d96da5f4a5c265334acc281c944e3d7b Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Wed, 2 Jul 2025 23:36:08 +0100 Subject: [PATCH 3/3] bewak up long paragraph --- Doc/reference/compound_stmts.rst | 41 ++++++++++++++++---------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst index f6d2f21e0d2511..2efaccf7978345 100644 --- a/Doc/reference/compound_stmts.rst +++ b/Doc/reference/compound_stmts.rst @@ -338,23 +338,28 @@ stored in the :mod:`sys` module is reset to its previous value:: The :keyword:`!except*` clause(s) specify one or more handlers for groups of exceptions (:exc:`BaseExceptionGroup` instances). A :keyword:`try` statement can have either :keyword:`except` or :keyword:`!except*` clauses, but not both. -The exception type for matching is interpreted as in the case of +The exception type for matching is mandatory in the case of :keyword:`!except*`, +so ``except*:`` is a syntax error. The type is interpreted as in the case of :keyword:`except`, but matching is performed on the exceptions contained in the -group that is being handled. Each :keyword:`!except*` clause splits (see -:meth:`~BaseExceptionGroup.split`) the exception group 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 ``sys.exception()``) and -assigned to the target of the :keyword:`!except*` clause (if there is one). Then, -the body of the :keyword:`!except*` clause is executed. If the non-matching +group that is being handled. An :exc:`TypeError` is raised if a matching +type is a subclass of :exc:`BaseExceptionGroup`, because that would have +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 ``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, +same manner. This continues until all exceptions in the group have been matched, or the last :keyword:`!except*` clause has run. -After the last :keyword:`!except*` runs, and before the :keyword:`!finally` block -(if there is one) executes, 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. +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. + and before the :keyword:`!finally` >>> try: ... raise ExceptionGroup("eg", ... [ValueError(1), TypeError(2), OSError(3), OSError(4)]) @@ -366,8 +371,10 @@ clauses. This merged exception group propagates on. caught with nested (TypeError(2),) caught with nested (OSError(3), OSError(4)) + Exception Group Traceback (most recent call last): - | File "", line 2, in - | ExceptionGroup: eg + | File "", line 2, in + | raise ExceptionGroup("eg", + | [ValueError(1), TypeError(2), OSError(3), OSError(4)]) + | ExceptionGroup: eg (1 sub-exception) +-+---------------- 1 ---------------- | ValueError: 1 +------------------------------------ @@ -384,12 +391,6 @@ type of the target ``e`` is consistently :exc:`BaseExceptionGroup`:: ... ExceptionGroup('', (BlockingIOError())) -An :keyword:`!except*` clause must have a matching expression; it cannot be ``except*:``. -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`. :keyword:`break`, :keyword:`continue` and :keyword:`return` cannot appear in an :keyword:`!except*` clause.