From eabe61d37c0cd682ff9e09ca05eca2d18736706f Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Mon, 30 Jun 2025 23:11:10 +0100 Subject: [PATCH 1/5] 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 7ac4d8587ce7d5..a518c9fce07ed1 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 aaa70403751a485961a96dc0b30e85c07a88df91 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Mon, 30 Jun 2025 23:17:34 +0100 Subject: [PATCH 2/5] 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 a518c9fce07ed1..aed201e81913c1 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 de721609641328cd3a337237e7a4913c9491d4d3 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Wed, 2 Jul 2025 23:36:08 +0100 Subject: [PATCH 3/5] 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 aed201e81913c1..8b86d5619eace4 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. From f3963070188c7a28bf2e1a9f090c480d6bdf28ca Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Mon, 4 Aug 2025 17:16:01 +0100 Subject: [PATCH 4/5] fix formatting --- Doc/reference/compound_stmts.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst index 8b86d5619eace4..f2cb3243d2eca4 100644 --- a/Doc/reference/compound_stmts.rst +++ b/Doc/reference/compound_stmts.rst @@ -357,9 +357,8 @@ 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. +: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)]) From f686a2954da61b679579f7a0d8f751574c02f554 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Mon, 4 Aug 2025 17:51:23 +0100 Subject: [PATCH 5/5] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/reference/compound_stmts.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst index f2cb3243d2eca4..5b64ff251651ad 100644 --- a/Doc/reference/compound_stmts.rst +++ b/Doc/reference/compound_stmts.rst @@ -340,15 +340,15 @@ 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 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 +:keyword:`!except`, but matching is performed on the exceptions contained in the group that is being handled. An :exc:`TypeError` is raised if a matching -type is a subclass of :exc:`BaseExceptionGroup`, because that would have +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()``) +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