From f42d81e34a99c678aecfa74f5b779113b5e28932 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Thu, 16 Dec 2021 22:15:14 +0000 Subject: [PATCH 01/17] bpo-45292: except* documentation --- Doc/library/exceptions.rst | 57 +++++++++++++++ Doc/reference/compound_stmts.rst | 47 +++++++++++- Doc/tutorial/errors.rst | 121 +++++++++++++++++++++++++++++++ 3 files changed, 224 insertions(+), 1 deletion(-) diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index 12d7d8abb26504..6c930b984b5266 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -851,6 +851,63 @@ 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 hierarcy 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 wrap only subclasses of :exc:`Exception`. This is so that + ``except Exception`` catches an :exc:`ExceptionGroup` but not + :exc:`BaseExceptionGroup`. + + It is usaully not necessary for a program to explicitly create a + :exc:`BaseExceptionGroup`, because the :exc:`ExceptionGroup` constructor + inspects the contained exceptions, and if any of them are not of type + :exc:`Exception` it returns a :exc:`BaseExceptionGroup` rather than an + :exc:`ExceptionGroup`. However, this is not automatically true for + subclasses of :exc:`ExceptionGroup`. + + .. 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 ``msg``, ``__traceback__``, ``__cause__``, + ``__context__`` and ``__note__`` fields. Empty nested groups are omitted + from the result. + + .. method:: split(condition) + + Like :meth:`subgroup`, but returns the pair ``(match, rest)`` where ``match`` + is ``subgroup(condition)`` and ``rest`` is ``subgroup(not condition)``. + + .. method:: derive(excs) + + Returns an exception group with the same ``msg``, ``__traceback__``, + ``__cause__``, ``__context__`` and ``__note__`` but which wraps the + exceptions in ``excs``. This method is used by :meth:`subgroup` and + :meth:`split` and may need to be overridden in subclasses if there are + additional values that need to be copied over to the result. + + .. versionadded:: 3.11 + Exception hierarchy ------------------- diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst index cf8ad1787b2915..d956d610c223e6 100644 --- a/Doc/reference/compound_stmts.rst +++ b/Doc/reference/compound_stmts.rst @@ -237,12 +237,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` @@ -325,6 +329,47 @@ when leaving an exception handler:: >>> print(sys.exc_info()) (None, None, None) +.. index:: + keyword:: except_star + +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 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 with nested (TypeError(2),) + caught with nested (OSError(3), OSError(4)) + + Exception Group Traceback (most recent call last): + | File "", line 2, in + | 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 by 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 diff --git a/Doc/tutorial/errors.rst b/Doc/tutorial/errors.rst index f2490d65db5d49..8f47ec3d5da324 100644 --- a/Doc/tutorial/errors.rst +++ b/Doc/tutorial/errors.rst @@ -496,3 +496,124 @@ 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 exceptions 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 "", line 1, in + | File "", 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 : 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 "", line 2, in + | File "", line 2, in f + | ExceptionGroup: group1 + +-+---------------- 1 ---------------- + | ExceptionGroup: group2 + +-+---------------- 1 ---------------- + | RecursionError: 4 + +------------------------------------ + + +Enriching Exceptions with Notes +=============================== + +When an exception is created in order to be raised, it is usually initialized +with information that describes the error that has occurred. There are cases +where it is useful to add information after the exception was caught. For this +purpose, exceptions have a mutable field ``__note__`` that can be assigned to +a string which is included in formatted tracebacks. + +For example, when collecting exceptions into an exception group, we may want +to add context information for the individual errors. In the following each +exception in the group has a note indicating when this error has occurred. :: + + >>> def f(): + ... raise OSError('operation failed') + ... + >>> excs = [] + >>> for i in range(3): + ... try: + ... f() + ... except Exception as e: + ... e.__note__ = f'Happened in Iteration {i+1}' + ... excs.append(e) + ... + >>> raise ExceptionGroup('We have some problems', excs) + + Exception Group Traceback (most recent call last): + | File "", line 1, in + | ExceptionGroup: We have some problems + +-+---------------- 1 ---------------- + | Traceback (most recent call last): + | File "", line 3, in + | File "", line 2, in f + | OSError: operation failed + | Happened in Iteration 1 + +---------------- 2 ---------------- + | Traceback (most recent call last): + | File "", line 3, in + | File "", line 2, in f + | OSError: operation failed + | Happened in Iteration 2 + +---------------- 3 ---------------- + | Traceback (most recent call last): + | File "", line 3, in + | File "", line 2, in f + | OSError: operation failed + | Happened in Iteration 3 + +------------------------------------ + >>> From 1eaebb2fc61be05092844c1adde7314d44cb3001 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Thu, 16 Dec 2021 22:28:37 +0000 Subject: [PATCH 02/17] fix --- Doc/library/exceptions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index 6c930b984b5266..03e07a5ece3854 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -857,7 +857,7 @@ Exception groups The following are used when it is necessary to raise multiple unrelated exceptions. They are part of the exception hierarcy so they can be handled with :keyword:`except` like all other exceptions. In addition, -they are recognised by :keyword:`except*!except_star`, which matches +they are recognised by :keyword:`except*`, which matches their subgroups based on the types of the contained exceptions. .. exception:: ExceptionGroup(msg, excs) From 59ae0f5a42c53af867c253f23e5614745cd07bf0 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Fri, 17 Dec 2021 06:20:56 +0000 Subject: [PATCH 03/17] Apply suggestions from code review Co-authored-by: Carol Willing --- Doc/library/exceptions.rst | 6 +++--- Doc/reference/compound_stmts.rst | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index 03e07a5ece3854..fd630152344b03 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -855,7 +855,7 @@ Exception groups ---------------- The following are used when it is necessary to raise multiple unrelated -exceptions. They are part of the exception hierarcy so they can be +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*`, which matches their subgroups based on the types of the contained exceptions. @@ -867,7 +867,7 @@ their subgroups based on the types of the contained exceptions. 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 wrap only subclasses of :exc:`Exception`. This is so that + and it can wrap only subclasses of :exc:`Exception`. This design is so that ``except Exception`` catches an :exc:`ExceptionGroup` but not :exc:`BaseExceptionGroup`. @@ -875,7 +875,7 @@ their subgroups based on the types of the contained exceptions. :exc:`BaseExceptionGroup`, because the :exc:`ExceptionGroup` constructor inspects the contained exceptions, and if any of them are not of type :exc:`Exception` it returns a :exc:`BaseExceptionGroup` rather than an - :exc:`ExceptionGroup`. However, this is not automatically true for + :exc:`ExceptionGroup`. However, this behavior is not automatically true for subclasses of :exc:`ExceptionGroup`. .. method:: subgroup(condition) diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst index d956d610c223e6..c25dd4b4e87c11 100644 --- a/Doc/reference/compound_stmts.rst +++ b/Doc/reference/compound_stmts.rst @@ -330,7 +330,7 @@ when leaving an exception handler:: (None, None, None) .. index:: - keyword:: except_star + keyword: except_star The :keyword:`except*` clause(s) are used for handling :exc:`ExceptionGroup`s. The exception type for matching is interpreted as in From af959d7834cfb6b1f480729433c74fa837906caf Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Fri, 17 Dec 2021 11:28:48 +0000 Subject: [PATCH 04/17] add _except_star anchor --- Doc/reference/compound_stmts.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst index c25dd4b4e87c11..14f6ddfa7225cb 100644 --- a/Doc/reference/compound_stmts.rst +++ b/Doc/reference/compound_stmts.rst @@ -220,6 +220,7 @@ returns the list ``[0, 1, 2]``. .. _try: .. _except: +.. _except_star: .. _finally: The :keyword:`!try` statement From 27db9c3f43197c609dcd64d6326b00b936c8de17 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Fri, 17 Dec 2021 11:29:23 +0000 Subject: [PATCH 05/17] add small note example --- Doc/tutorial/errors.rst | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Doc/tutorial/errors.rst b/Doc/tutorial/errors.rst index 8f47ec3d5da324..5f183f071ee49a 100644 --- a/Doc/tutorial/errors.rst +++ b/Doc/tutorial/errors.rst @@ -567,7 +567,7 @@ other clauses and eventually to be reraised. :: +-+---------------- 1 ---------------- | RecursionError: 4 +------------------------------------ - + >>> Enriching Exceptions with Notes =============================== @@ -576,7 +576,19 @@ When an exception is created in order to be raised, it is usually initialized with information that describes the error that has occurred. There are cases where it is useful to add information after the exception was caught. For this purpose, exceptions have a mutable field ``__note__`` that can be assigned to -a string which is included in formatted tracebacks. +a string which is included in formatted tracebacks. :: + + >>> try: + ... raise TypeError('bad type') + ... except Exception as e: + ... e.__note__ = 'Add some information' + ... raise + ... + Traceback (most recent call last): + File "", line 2, in + TypeError: bad type + Add some information + >>> For example, when collecting exceptions into an exception group, we may want to add context information for the individual errors. In the following each From 274f7f1982b76ea3e8dfbd1864342b690dc98901 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Fri, 17 Dec 2021 11:44:24 +0000 Subject: [PATCH 06/17] mentioned __note__ in traceback doc --- Doc/library/traceback.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Doc/library/traceback.rst b/Doc/library/traceback.rst index df4a38c955511b..63568d4c1f1acc 100644 --- a/Doc/library/traceback.rst +++ b/Doc/library/traceback.rst @@ -236,6 +236,12 @@ capture data for later printing in a lightweight fashion. The ``__suppress_context__`` value from the original exception. + .. attribute:: __note__ + + The ``__note__`` value from the original exception. + + .. versionadded:: 3.11 + .. attribute:: stack A :class:`StackSummary` representing the traceback. From 9f9029272ec45f51e4ba2838ed2f29ff138653c0 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Fri, 17 Dec 2021 12:47:29 +0000 Subject: [PATCH 07/17] Apply suggestions from code review Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> --- Doc/library/exceptions.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index fd630152344b03..67b03d961da7b4 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -867,11 +867,11 @@ their subgroups based on the types of the contained exceptions. 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 wrap only subclasses of :exc:`Exception`. This design is so that + and it can only wrap subclasses of :exc:`Exception`. This design is so that ``except Exception`` catches an :exc:`ExceptionGroup` but not :exc:`BaseExceptionGroup`. - It is usaully not necessary for a program to explicitly create a + It is usually not necessary for a program to explicitly create a :exc:`BaseExceptionGroup`, because the :exc:`ExceptionGroup` constructor inspects the contained exceptions, and if any of them are not of type :exc:`Exception` it returns a :exc:`BaseExceptionGroup` rather than an From a285f87cca1e4ce548c26867f584bf2ab4821b7d Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Sun, 19 Dec 2021 15:34:01 +0000 Subject: [PATCH 08/17] typo Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> --- Doc/reference/compound_stmts.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst index 14f6ddfa7225cb..12fac9ed9bd193 100644 --- a/Doc/reference/compound_stmts.rst +++ b/Doc/reference/compound_stmts.rst @@ -364,7 +364,7 @@ one except* clause, the first that matches it. :: 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 by a + 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* From 6a99f8c76b0efb5e45a9efec3470037440a2f86e Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Sun, 19 Dec 2021 15:41:50 +0000 Subject: [PATCH 09/17] nested exceptions must be instances --- Doc/tutorial/errors.rst | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/Doc/tutorial/errors.rst b/Doc/tutorial/errors.rst index 5f183f071ee49a..83c802835aac9a 100644 --- a/Doc/tutorial/errors.rst +++ b/Doc/tutorial/errors.rst @@ -509,9 +509,9 @@ 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 exceptions so that they can -be raised together. It is an exception itself, so it can be caught like any -other 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')] @@ -569,6 +569,23 @@ other clauses and eventually to be reraised. :: +------------------------------------ >>> +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) + ... + + Enriching Exceptions with Notes =============================== From 7a13adb3afde28dc2203e808c6fa74210e1e7f37 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Sun, 19 Dec 2021 16:06:07 +0000 Subject: [PATCH 10/17] subgroup/split check condition for interior nodes as well. This mean that rest is not exactly subgroup(not condition) --- Doc/library/exceptions.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index 67b03d961da7b4..438c439308b73f 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -893,10 +893,15 @@ their subgroups based on the types of the contained exceptions. ``__context__`` and ``__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 ``subgroup(not condition)``. + is ``subgroup(condition)`` and ``rest`` is the remainder, the non-matching + part. .. method:: derive(excs) From be728bf78035623d38aa346c0e3d5905e67c9f6f Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Mon, 20 Dec 2021 19:56:28 +0000 Subject: [PATCH 11/17] =?UTF-8?q?Kumar=E2=80=99s=20edit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> --- Doc/library/exceptions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index 438c439308b73f..77da9d70d2e435 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -900,7 +900,7 @@ their subgroups based on the types of the contained exceptions. .. method:: split(condition) Like :meth:`subgroup`, but returns the pair ``(match, rest)`` where ``match`` - is ``subgroup(condition)`` and ``rest`` is the remainder, the non-matching + is ``subgroup(condition)`` and ``rest`` is the remaining non-matching part. .. method:: derive(excs) From cbff13f87da6d264e325bc6cd4e87b7c20992dbe Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Mon, 20 Dec 2021 23:56:40 +0000 Subject: [PATCH 12/17] attribute references --- Doc/library/exceptions.rst | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index 77da9d70d2e435..c46533a97a3d16 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -889,9 +889,9 @@ their subgroups based on the types of the contained exceptions. 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 ``msg``, ``__traceback__``, ``__cause__``, - ``__context__`` and ``__note__`` fields. Empty nested groups are omitted - from 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 @@ -905,11 +905,12 @@ their subgroups based on the types of the contained exceptions. .. method:: derive(excs) - Returns an exception group with the same ``msg``, ``__traceback__``, - ``__cause__``, ``__context__`` and ``__note__`` but which wraps the - exceptions in ``excs``. This method is used by :meth:`subgroup` and - :meth:`split` and may need to be overridden in subclasses if there are - additional values that need to be copied over to the result. + 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` and + may need to be overridden in subclasses if there are additional + values that need to be copied over to the result. .. versionadded:: 3.11 From 2b76b038505db4452b16ab75da710adde2aa0842 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Tue, 21 Dec 2021 00:00:08 +0000 Subject: [PATCH 13/17] reword __note__ description --- Doc/library/traceback.rst | 4 +++- Doc/tutorial/errors.rst | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Doc/library/traceback.rst b/Doc/library/traceback.rst index 63568d4c1f1acc..766eb46bdc8c6e 100644 --- a/Doc/library/traceback.rst +++ b/Doc/library/traceback.rst @@ -238,7 +238,9 @@ capture data for later printing in a lightweight fashion. .. attribute:: __note__ - The ``__note__`` value from the original exception. + The ``__note__`` value from the original exception, which is + string or ``None``. If it is not ``None`` is it formatted in + the traceback after the exception string. .. versionadded:: 3.11 diff --git a/Doc/tutorial/errors.rst b/Doc/tutorial/errors.rst index 83c802835aac9a..b007d024b98202 100644 --- a/Doc/tutorial/errors.rst +++ b/Doc/tutorial/errors.rst @@ -592,8 +592,9 @@ Enriching Exceptions with Notes When an exception is created in order to be raised, it is usually initialized with information that describes the error that has occurred. There are cases where it is useful to add information after the exception was caught. For this -purpose, exceptions have a mutable field ``__note__`` that can be assigned to -a string which is included in formatted tracebacks. :: +purpose, exceptions have a mutable field ``__note__`` that can be string or +``None`` (``None`` by default). If it is a string, it is included in the +formatted tracebacks after the exception. :: >>> try: ... raise TypeError('bad type') From dd967c2123e0635b9931af441645fda952a9573f Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Wed, 22 Dec 2021 19:09:05 +0000 Subject: [PATCH 14/17] fix the contructor description --- Doc/library/exceptions.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index c46533a97a3d16..bceccb00dd4e21 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -871,12 +871,12 @@ their subgroups based on the types of the contained exceptions. ``except Exception`` catches an :exc:`ExceptionGroup` but not :exc:`BaseExceptionGroup`. - It is usually not necessary for a program to explicitly create a - :exc:`BaseExceptionGroup`, because the :exc:`ExceptionGroup` constructor - inspects the contained exceptions, and if any of them are not of type - :exc:`Exception` it returns a :exc:`BaseExceptionGroup` rather than an - :exc:`ExceptionGroup`. However, this behavior is not automatically true for - subclasses of :exc:`ExceptionGroup`. + 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) From a964a2d9166a47f38634ca3511b20bf0268cea0d Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Tue, 4 Jan 2022 11:07:49 +0000 Subject: [PATCH 15/17] add subclassing example --- Doc/library/exceptions.rst | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index bceccb00dd4e21..f90b6761154af5 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -908,9 +908,18 @@ their subgroups based on the types of the contained exceptions. 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` and - may need to be overridden in subclasses if there are additional - values that need to be copied over to the result. + + 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 From 939a1f65054a50fc5414a9af496d17d00eb7d7f0 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Thu, 6 Jan 2022 18:16:14 +0000 Subject: [PATCH 16/17] remove __note__ documentation --- Doc/library/traceback.rst | 8 ----- Doc/tutorial/errors.rst | 62 --------------------------------------- 2 files changed, 70 deletions(-) diff --git a/Doc/library/traceback.rst b/Doc/library/traceback.rst index 766eb46bdc8c6e..df4a38c955511b 100644 --- a/Doc/library/traceback.rst +++ b/Doc/library/traceback.rst @@ -236,14 +236,6 @@ capture data for later printing in a lightweight fashion. The ``__suppress_context__`` value from the original exception. - .. attribute:: __note__ - - The ``__note__`` value from the original exception, which is - string or ``None``. If it is not ``None`` is it formatted in - the traceback after the exception string. - - .. versionadded:: 3.11 - .. attribute:: stack A :class:`StackSummary` representing the traceback. diff --git a/Doc/tutorial/errors.rst b/Doc/tutorial/errors.rst index b007d024b98202..47403e0c031b10 100644 --- a/Doc/tutorial/errors.rst +++ b/Doc/tutorial/errors.rst @@ -585,65 +585,3 @@ the following pattern:: ... raise ExceptionGroup("Test Failures", excs) ... - -Enriching Exceptions with Notes -=============================== - -When an exception is created in order to be raised, it is usually initialized -with information that describes the error that has occurred. There are cases -where it is useful to add information after the exception was caught. For this -purpose, exceptions have a mutable field ``__note__`` that can be string or -``None`` (``None`` by default). If it is a string, it is included in the -formatted tracebacks after the exception. :: - - >>> try: - ... raise TypeError('bad type') - ... except Exception as e: - ... e.__note__ = 'Add some information' - ... raise - ... - Traceback (most recent call last): - File "", line 2, in - TypeError: bad type - Add some information - >>> - -For example, when collecting exceptions into an exception group, we may want -to add context information for the individual errors. In the following each -exception in the group has a note indicating when this error has occurred. :: - - >>> def f(): - ... raise OSError('operation failed') - ... - >>> excs = [] - >>> for i in range(3): - ... try: - ... f() - ... except Exception as e: - ... e.__note__ = f'Happened in Iteration {i+1}' - ... excs.append(e) - ... - >>> raise ExceptionGroup('We have some problems', excs) - + Exception Group Traceback (most recent call last): - | File "", line 1, in - | ExceptionGroup: We have some problems - +-+---------------- 1 ---------------- - | Traceback (most recent call last): - | File "", line 3, in - | File "", line 2, in f - | OSError: operation failed - | Happened in Iteration 1 - +---------------- 2 ---------------- - | Traceback (most recent call last): - | File "", line 3, in - | File "", line 2, in f - | OSError: operation failed - | Happened in Iteration 2 - +---------------- 3 ---------------- - | Traceback (most recent call last): - | File "", line 3, in - | File "", line 2, in f - | OSError: operation failed - | Happened in Iteration 3 - +------------------------------------ - >>> From 4a465705fe423e0f004c71203dc0e48abfc070fe Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Thu, 6 Jan 2022 18:25:45 +0000 Subject: [PATCH 17/17] Fix prompt in example --- Doc/tutorial/errors.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/tutorial/errors.rst b/Doc/tutorial/errors.rst index 47403e0c031b10..33c6a608802127 100644 --- a/Doc/tutorial/errors.rst +++ b/Doc/tutorial/errors.rst @@ -581,7 +581,7 @@ the following pattern:: ... except Exception as e: ... excs.append(e) ... - ... if excs: + >>> if excs: ... raise ExceptionGroup("Test Failures", excs) ...