Skip to main content

Objects/exceptions.c (part 8)

Source:

cpython 3.14 @ ab2d84fe1023/Objects/exceptions.c

This annotation covers ExceptionGroup (PEP 654). See objects_exceptions7_detail for BaseException.__new__, __traceback__, __cause__, and exception chaining.

Map

LinesSymbolRole
1-80BaseExceptionGroup.__new__Validate message + exceptions list
81-160ExceptionGroup vs BaseExceptionGroupWhich type to use
161-240BaseExceptionGroup.splitPartition into matching/non-matching groups
241-360BaseExceptionGroup.subgroupFilter to only matching exceptions
361-500BaseExceptionGroup.deriveCreate a new group with the same message

Reading

BaseExceptionGroup.__new__

// CPython: Objects/exceptions.c:3280 BaseExceptionGroup_new
static PyObject *
BaseExceptionGroup_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
PyObject *message, *excs;
PyArg_ParseTuple(args, "UO:BaseExceptionGroup", &message, &excs);
excs = PySequence_Tuple(excs); /* normalize to tuple */
/* Validate: each exception must be a BaseException */
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(excs); i++) {
if (!PyExceptionInstance_Check(PyTuple_GET_ITEM(excs, i))) {
PyErr_Format(PyExc_TypeError,
"second argument ... must contain BaseExceptions");
return NULL;
}
}
...
}

ExceptionGroup("errors", [ValueError(1), TypeError(2)]) creates a group. The exceptions must be instances, not classes. The message is stored in args[0] (the standard BaseException.args convention).

ExceptionGroup vs BaseExceptionGroup

# CPython: Objects/exceptions.c:3320 type selection
# If all contained exceptions are Exception (not BaseException) subclasses:
# → type is ExceptionGroup (subclass of Exception + BaseExceptionGroup)
# If any is a BaseException (like KeyboardInterrupt, SystemExit):
# → type is BaseExceptionGroup (not a subclass of Exception)

# This matters for except* clauses:
# except* Exception: catches ExceptionGroup
# Does NOT catch BaseExceptionGroup containing SystemExit

The constructor selects the concrete type automatically. If ValueError and KeyboardInterrupt are mixed, the result is a BaseExceptionGroup (since KeyboardInterrupt is not an Exception).

BaseExceptionGroup.split

# CPython: Objects/exceptions.c:3440 BaseExceptionGroup_split
def split(self, condition):
# condition: a type, or a callable(exc) -> bool
match = []
rest = []
for exc in self.exceptions:
if isinstance(exc, BaseExceptionGroup):
m, r = exc.split(condition)
if m: match.append(m)
if r: rest.append(r)
elif _matches(exc, condition):
match.append(exc)
else:
rest.append(exc)
match_group = self.derive(match) if match else None
rest_group = self.derive(rest) if rest else None
return match_group, rest_group

split recursively partitions nested groups. except* ValueError as eg: uses split to extract matching exceptions and re-raise the rest. Both halves are new ExceptionGroup objects.

BaseExceptionGroup.subgroup

# CPython: Objects/exceptions.c:3500 BaseExceptionGroup_subgroup
def subgroup(self, condition):
match, _ = self.split(condition)
return match

subgroup is split without the "rest" half. Useful for filtering: eg.subgroup(ValueError) returns only the ValueError instances (recursively).

BaseExceptionGroup.derive

# CPython: Objects/exceptions.c:3380 BaseExceptionGroup_derive
def derive(self, excs):
# Create a new group of the same type with the same message
return type(self)(self.args[0], excs)

derive is the factory method for creating a new group with the same message string. It uses type(self) so subclasses of ExceptionGroup produce the same subclass type.

gopy notes

BaseExceptionGroup.__new__ is objects.NewExceptionGroup in objects/exceptions.go. split is a recursive Go method on objects.ExceptionGroup. subgroup calls split and discards the second return. derive calls objects.NewExceptionGroup(msg, excs).