Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[3.14] Fix ExceptionGroup repr changing when original exception sequence is mutated (GH-141736)#144445

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
dr-carlos wants to merge6 commits intopython:3.14
base:3.14
Choose a base branch
Loading
fromdr-carlos:backport-ff2577f-3.14
Open
Show file tree
Hide file tree
Changes fromall 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
6 changes: 6 additions & 0 deletionsDoc/library/exceptions.rst
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -971,6 +971,12 @@ their subgroups based on the types of the contained exceptions.
raises a :exc:`TypeError` if any contained exception is not an
:exc:`Exception` subclass.

.. impl-detail::

The ``excs`` parameter may be any sequence, but lists and tuples are
specifically processed more efficiently here. For optimal performance,
pass a tuple as ``excs``.

.. attribute:: message

The ``msg`` argument to the constructor. This is a read-only attribute.
Expand Down
1 change: 1 addition & 0 deletionsInclude/cpython/pyerrors.h
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,7 @@ typedef struct {
PyException_HEAD
PyObject *msg;
PyObject *excs;
PyObject *excs_str;
} PyBaseExceptionGroupObject;

typedef struct {
Expand Down
73 changes: 72 additions & 1 deletionLib/test/test_exception_group.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
import collections.abc
import collections
import types
import unittest
from test.support import skip_emscripten_stack_overflow, skip_wasi_stack_overflow, exceeds_recursion_limit
Expand DownExpand Up@@ -193,6 +193,77 @@ class MyEG(ExceptionGroup):
"MyEG('flat', [ValueError(1), TypeError(2)]), "
"TypeError(2)])"))

def test_exceptions_mutation(self):
class MyEG(ExceptionGroup):
pass

excs = [ValueError(1), TypeError(2)]
eg = MyEG('test', excs)

self.assertEqual(repr(eg), "MyEG('test', [ValueError(1), TypeError(2)])")
excs.clear()

# Ensure that clearing the exceptions sequence doesn't change the repr.
self.assertEqual(repr(eg), "MyEG('test', [ValueError(1), TypeError(2)])")

# Ensure that the args are still as passed.
self.assertEqual(eg.args, ('test', []))

excs = (ValueError(1), KeyboardInterrupt(2))
eg = BaseExceptionGroup('test', excs)

# Ensure that immutable sequences still work fine.
self.assertEqual(
repr(eg),
"BaseExceptionGroup('test', (ValueError(1), KeyboardInterrupt(2)))"
)

# Test non-standard custom sequences.
excs = collections.deque([ValueError(1), TypeError(2)])
eg = ExceptionGroup('test', excs)

self.assertEqual(
repr(eg),
"ExceptionGroup('test', deque([ValueError(1), TypeError(2)]))"
)
excs.clear()

# Ensure that clearing the exceptions sequence doesn't change the repr.
self.assertEqual(
repr(eg),
"ExceptionGroup('test', deque([ValueError(1), TypeError(2)]))"
)

def test_repr_raises(self):
class MySeq(collections.abc.Sequence):
def __init__(self, raises):
self.raises = raises

def __len__(self):
return 1

def __getitem__(self, index):
if index == 0:
return ValueError(1)
raise IndexError

def __repr__(self):
if self.raises:
raise self.raises
return None

seq = MySeq(None)
with self.assertRaisesRegex(
TypeError,
r"__repr__ returned non-string \(type NoneType\)"
):
ExceptionGroup("test", seq)

seq = MySeq(ValueError)
with self.assertRaises(ValueError):
BaseExceptionGroup("test", seq)



def create_simple_eg():
excs = []
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
Ensure the :meth:`~object.__repr__` for :exc:`ExceptionGroup` and :exc:`BaseExceptionGroup` does
not change when the exception sequence that was original passed in to its constructor is subsequently mutated.
89 changes: 77 additions & 12 deletionsObjects/exceptions.c
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -695,12 +695,12 @@ PyTypeObject _PyExc_ ## EXCNAME = { \

#define ComplexExtendsException(EXCBASE, EXCNAME, EXCSTORE, EXCNEW, \
EXCMETHODS, EXCMEMBERS, EXCGETSET, \
EXCSTR, EXCDOC) \
EXCSTR,EXCREPR,EXCDOC) \
static PyTypeObject _PyExc_ ## EXCNAME = { \
PyVarObject_HEAD_INIT(NULL, 0) \
# EXCNAME, \
sizeof(Py ## EXCSTORE ## Object), 0, \
EXCSTORE ## _dealloc, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, \
EXCSTORE ## _dealloc, 0, 0, 0, 0,EXCREPR, 0, 0, 0, 0, 0, \
EXCSTR, 0, 0, 0, \
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, \
PyDoc_STR(EXCDOC), EXCSTORE ## _traverse, \
Expand DownExpand Up@@ -793,7 +793,7 @@ StopIteration_traverse(PyObject *op, visitproc visit, void *arg)
}

ComplexExtendsException(PyExc_Exception, StopIteration, StopIteration,
0, 0, StopIteration_members, 0, 0,
0, 0, StopIteration_members, 0, 0, 0,
"Signal the end from iterator.__next__().");


Expand DownExpand Up@@ -866,7 +866,7 @@ static PyMemberDef SystemExit_members[] = {
};

ComplexExtendsException(PyExc_BaseException, SystemExit, SystemExit,
0, 0, SystemExit_members, 0, 0,
0, 0, SystemExit_members, 0, 0, 0,
"Request to exit from the interpreter.");

/*
Expand All@@ -891,6 +891,7 @@ BaseExceptionGroup_new(PyTypeObject *type, PyObject *args, PyObject *kwds)

PyObject *message = NULL;
PyObject *exceptions = NULL;
PyObject *exceptions_str = NULL;

if (!PyArg_ParseTuple(args,
"UO:BaseExceptionGroup.__new__",
Expand All@@ -906,6 +907,18 @@ BaseExceptionGroup_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return NULL;
}

/* Save initial exceptions sequence as a string in case sequence is mutated */
if (!PyList_Check(exceptions) && !PyTuple_Check(exceptions)) {
exceptions_str = PyObject_Repr(exceptions);
if (exceptions_str == NULL) {
/* We don't hold a reference to exceptions, so clear it before
* attempting a decref in the cleanup.
*/
exceptions = NULL;
goto error;
}
}

exceptions = PySequence_Tuple(exceptions);
if (!exceptions) {
return NULL;
Expand DownExpand Up@@ -989,9 +1002,11 @@ BaseExceptionGroup_new(PyTypeObject *type, PyObject *args, PyObject *kwds)

self->msg = Py_NewRef(message);
self->excs = exceptions;
self->excs_str = exceptions_str;
return (PyObject*)self;
error:
Py_DECREF(exceptions);
Py_XDECREF(exceptions);
Py_XDECREF(exceptions_str);
return NULL;
}

Expand DownExpand Up@@ -1030,6 +1045,7 @@ BaseExceptionGroup_clear(PyObject *op)
PyBaseExceptionGroupObject *self = PyBaseExceptionGroupObject_CAST(op);
Py_CLEAR(self->msg);
Py_CLEAR(self->excs);
Py_CLEAR(self->excs_str);
return BaseException_clear(op);
}

Expand All@@ -1047,6 +1063,7 @@ BaseExceptionGroup_traverse(PyObject *op, visitproc visit, void *arg)
PyBaseExceptionGroupObject *self = PyBaseExceptionGroupObject_CAST(op);
Py_VISIT(self->msg);
Py_VISIT(self->excs);
Py_VISIT(self->excs_str);
return BaseException_traverse(op, visit, arg);
}

Expand All@@ -1064,6 +1081,54 @@ BaseExceptionGroup_str(PyObject *op)
self->msg, num_excs, num_excs > 1 ? "s" : "");
}

static PyObject *
BaseExceptionGroup_repr(PyObject *op)
{
PyBaseExceptionGroupObject *self = PyBaseExceptionGroupObject_CAST(op);
assert(self->msg);

PyObject *exceptions_str = NULL;

/* Use the saved exceptions string for custom sequences. */
if (self->excs_str) {
exceptions_str = Py_NewRef(self->excs_str);
}
else {
assert(self->excs);

/* Older versions delegated to BaseException, inserting the current
* value of self.args[1]; but this can be mutable and go out-of-sync
* with self.exceptions. Instead, use self.exceptions for accuracy,
* making it look like self.args[1] for backwards compatibility. */
if (PyList_Check(PyTuple_GET_ITEM(self->args, 1))) {
PyObject *exceptions_list = PySequence_List(self->excs);
if (!exceptions_list) {
return NULL;
}

exceptions_str = PyObject_Repr(exceptions_list);
Py_DECREF(exceptions_list);
}
else {
exceptions_str = PyObject_Repr(self->excs);
}

if (!exceptions_str) {
return NULL;
}
}

assert(exceptions_str != NULL);

const char *name = _PyType_Name(Py_TYPE(self));
PyObject *repr = PyUnicode_FromFormat(
"%s(%R, %U)", name,
self->msg, exceptions_str);

Py_DECREF(exceptions_str);
return repr;
}

/*[clinic input]
@critical_section
BaseExceptionGroup.derive
Expand DownExpand Up@@ -1698,7 +1763,7 @@ static PyMethodDef BaseExceptionGroup_methods[] = {
ComplexExtendsException(PyExc_BaseException, BaseExceptionGroup,
BaseExceptionGroup, BaseExceptionGroup_new /* new */,
BaseExceptionGroup_methods, BaseExceptionGroup_members,
0 /* getset */, BaseExceptionGroup_str,
0 /* getset */, BaseExceptionGroup_str, BaseExceptionGroup_repr,
"A combination of multiple unrelated exceptions.");

/*
Expand DownExpand Up@@ -1884,7 +1949,7 @@ static PyMethodDef ImportError_methods[] = {
ComplexExtendsException(PyExc_Exception, ImportError,
ImportError, 0 /* new */,
ImportError_methods, ImportError_members,
0 /* getset */, ImportError_str,
0 /* getset */, ImportError_str, 0,
"Import can't find module, or can't find name in "
"module.");

Expand DownExpand Up@@ -2356,7 +2421,7 @@ static PyGetSetDef OSError_getset[] = {
ComplexExtendsException(PyExc_Exception, OSError,
OSError, OSError_new,
OSError_methods, OSError_members, OSError_getset,
OSError_str,
OSError_str, 0,
"Base class for I/O related errors.");


Expand DownExpand Up@@ -2497,7 +2562,7 @@ static PyMethodDef NameError_methods[] = {
ComplexExtendsException(PyExc_Exception, NameError,
NameError, 0,
NameError_methods, NameError_members,
0, BaseException_str, "Name not found globally.");
0, BaseException_str,0,"Name not found globally.");

/*
* UnboundLocalError extends NameError
Expand DownExpand Up@@ -2631,7 +2696,7 @@ static PyMethodDef AttributeError_methods[] = {
ComplexExtendsException(PyExc_Exception, AttributeError,
AttributeError, 0,
AttributeError_methods, AttributeError_members,
0, BaseException_str, "Attribute not found.");
0, BaseException_str,0,"Attribute not found.");

/*
* SyntaxError extends Exception
Expand DownExpand Up@@ -2830,7 +2895,7 @@ static PyMemberDef SyntaxError_members[] = {

ComplexExtendsException(PyExc_Exception, SyntaxError, SyntaxError,
0, 0, SyntaxError_members, 0,
SyntaxError_str, "Invalid syntax.");
SyntaxError_str,0,"Invalid syntax.");


/*
Expand DownExpand Up@@ -2890,7 +2955,7 @@ KeyError_str(PyObject *op)
}

ComplexExtendsException(PyExc_LookupError, KeyError, BaseException,
0, 0, 0, 0, KeyError_str, "Mapping key not found.");
0, 0, 0, 0, KeyError_str,0,"Mapping key not found.");


/*
Expand Down
Loading

[8]ページ先頭

©2009-2026 Movatter.jp