Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Doc/reference/compound_stmts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,10 @@ Any remaining exceptions that were not handled by any :keyword:`!except*`
clause are re-raised at the end, combined into an exception group along with
all exceptions that were raised from within :keyword:`!except*` clauses.

From version 3.11.4, when the entire :exc:`ExceptionGroup` is handled and
only one exception is raised from an :keyword:`!except*` clause, this
exception is no longer wrapped to form a new :exc:`ExceptionGroup`.

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. ::
Expand Down
44 changes: 20 additions & 24 deletions Lib/test/test_except_star.py
Original file line number Diff line number Diff line change
Expand Up @@ -636,18 +636,17 @@ def test_raise_handle_all_raise_one_named(self):
raise orig
except* (TypeError, ValueError) as e:
raise SyntaxError(3)
except BaseException as e:
except SyntaxError as e:
exc = e

self.assertExceptionIsLike(
exc, ExceptionGroup("", [SyntaxError(3)]))
self.assertExceptionIsLike(exc, SyntaxError(3))

self.assertExceptionIsLike(
exc.exceptions[0].__context__,
exc.__context__,
ExceptionGroup("eg", [TypeError(1), ValueError(2)]))

self.assertMetadataNotEqual(orig, exc)
self.assertMetadataEqual(orig, exc.exceptions[0].__context__)
self.assertMetadataEqual(orig, exc.__context__)

def test_raise_handle_all_raise_one_unnamed(self):
orig = ExceptionGroup("eg", [TypeError(1), ValueError(2)])
Expand All @@ -656,18 +655,17 @@ def test_raise_handle_all_raise_one_unnamed(self):
raise orig
except* (TypeError, ValueError) as e:
raise SyntaxError(3)
except ExceptionGroup as e:
except SyntaxError as e:
exc = e

self.assertExceptionIsLike(
exc, ExceptionGroup("", [SyntaxError(3)]))
self.assertExceptionIsLike(exc, SyntaxError(3))

self.assertExceptionIsLike(
exc.exceptions[0].__context__,
exc.__context__,
ExceptionGroup("eg", [TypeError(1), ValueError(2)]))

self.assertMetadataNotEqual(orig, exc)
self.assertMetadataEqual(orig, exc.exceptions[0].__context__)
self.assertMetadataEqual(orig, exc.__context__)

def test_raise_handle_all_raise_two_named(self):
orig = ExceptionGroup("eg", [TypeError(1), ValueError(2)])
Expand Down Expand Up @@ -791,23 +789,22 @@ def test_raise_handle_all_raise_one_named(self):
raise orig
except* (TypeError, ValueError) as e:
raise SyntaxError(3) from e
except BaseException as e:
except SyntaxError as e:
exc = e

self.assertExceptionIsLike(
exc, ExceptionGroup("", [SyntaxError(3)]))
self.assertExceptionIsLike(exc, SyntaxError(3))

self.assertExceptionIsLike(
exc.exceptions[0].__context__,
exc.__context__,
ExceptionGroup("eg", [TypeError(1), ValueError(2)]))

self.assertExceptionIsLike(
exc.exceptions[0].__cause__,
exc.__cause__,
ExceptionGroup("eg", [TypeError(1), ValueError(2)]))

self.assertMetadataNotEqual(orig, exc)
self.assertMetadataEqual(orig, exc.exceptions[0].__context__)
self.assertMetadataEqual(orig, exc.exceptions[0].__cause__)
self.assertMetadataEqual(orig, exc.__context__)
self.assertMetadataEqual(orig, exc.__cause__)

def test_raise_handle_all_raise_one_unnamed(self):
orig = ExceptionGroup("eg", [TypeError(1), ValueError(2)])
Expand All @@ -817,23 +814,22 @@ def test_raise_handle_all_raise_one_unnamed(self):
except* (TypeError, ValueError) as e:
e = sys.exception()
raise SyntaxError(3) from e
except ExceptionGroup as e:
except SyntaxError as e:
exc = e

self.assertExceptionIsLike(
exc, ExceptionGroup("", [SyntaxError(3)]))
self.assertExceptionIsLike(exc, SyntaxError(3))

self.assertExceptionIsLike(
exc.exceptions[0].__context__,
exc.__context__,
ExceptionGroup("eg", [TypeError(1), ValueError(2)]))

self.assertExceptionIsLike(
exc.exceptions[0].__cause__,
exc.__cause__,
ExceptionGroup("eg", [TypeError(1), ValueError(2)]))

self.assertMetadataNotEqual(orig, exc)
self.assertMetadataEqual(orig, exc.exceptions[0].__context__)
self.assertMetadataEqual(orig, exc.exceptions[0].__cause__)
self.assertMetadataEqual(orig, exc.__context__)
self.assertMetadataEqual(orig, exc.__cause__)

def test_raise_handle_all_raise_two_named(self):
orig = ExceptionGroup("eg", [TypeError(1), ValueError(2)])
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Do not wrap a single exception raised from a ``try-except*`` construct in an :exc:`ExceptionGroup`.
7 changes: 6 additions & 1 deletion Objects/exceptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -1423,7 +1423,12 @@ _PyExc_PrepReraiseStar(PyObject *orig, PyObject *excs)
if (res < 0) {
goto done;
}
result = _PyExc_CreateExceptionGroup("", raised_list);
if (PyList_GET_SIZE(raised_list) > 1) {
result = _PyExc_CreateExceptionGroup("", raised_list);
}
else {
result = Py_NewRef(PyList_GetItem(raised_list, 0));
}
if (result == NULL) {
goto done;
}
Expand Down