Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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/whatsnew/3.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,10 @@ Other Language Changes
* :class:`slice` objects are now hashable, allowing them to be used as dict keys and
set items. (Contributed by Will Bradshaw and Furkan Onder in :gh:`101264`.)

* Exceptions raised in a typeobject's ``__set_name__`` method are no longer
wrapped by a :exc:`RuntimeError`. Context information is added to the
exception as a :pep:`678` note. (Contributed by Irit Katriel in :gh:`77757`.)

New Modules
===========

Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -2980,7 +2980,7 @@ class MyClass(metaclass=MyMeta):

def test_reuse_different_names(self):
"""Disallow this case because decorated function a would not be cached."""
with self.assertRaises(RuntimeError) as ctx:
with self.assertRaises(TypeError) as ctx:
class ReusedCachedProperty:
@py_functools.cached_property
def a(self):
Expand All @@ -2989,7 +2989,7 @@ def a(self):
b = a

self.assertEqual(
str(ctx.exception.__context__),
str(ctx.exception),
str(TypeError("Cannot assign the same cached_property to two different names ('a' and 'b')."))
)

Expand Down
22 changes: 10 additions & 12 deletions Lib/test/test_subclassinit.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,30 +134,28 @@ class Descriptor:
def __set_name__(self, owner, name):
1/0

with self.assertRaises(RuntimeError) as cm:
with self.assertRaises(ZeroDivisionError) as cm:
class NotGoingToWork:
attr = Descriptor()

exc = cm.exception
self.assertRegex(str(exc), r'\bNotGoingToWork\b')
self.assertRegex(str(exc), r'\battr\b')
self.assertRegex(str(exc), r'\bDescriptor\b')
self.assertIsInstance(exc.__cause__, ZeroDivisionError)
notes = cm.exception.__notes__
self.assertRegex(str(notes), r'\bNotGoingToWork\b')
self.assertRegex(str(notes), r'\battr\b')
self.assertRegex(str(notes), r'\bDescriptor\b')

def test_set_name_wrong(self):
class Descriptor:
def __set_name__(self):
pass

with self.assertRaises(RuntimeError) as cm:
with self.assertRaises(TypeError) as cm:
class NotGoingToWork:
attr = Descriptor()

exc = cm.exception
self.assertRegex(str(exc), r'\bNotGoingToWork\b')
self.assertRegex(str(exc), r'\battr\b')
self.assertRegex(str(exc), r'\bDescriptor\b')
self.assertIsInstance(exc.__cause__, TypeError)
notes = cm.exception.__notes__
self.assertRegex(str(notes), r'\bNotGoingToWork\b')
self.assertRegex(str(notes), r'\battr\b')
self.assertRegex(str(notes), r'\bDescriptor\b')

def test_set_name_lookup(self):
resolved = []
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Exceptions raised in a typeobject's ``__set_name__`` method are no longer
wrapped by a :exc:`RuntimeError`. Context information is added to the
exception as a :pep:`678` note.
19 changes: 17 additions & 2 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -9137,13 +9137,28 @@ type_new_set_names(PyTypeObject *type)
Py_DECREF(set_name);

if (res == NULL) {
_PyErr_FormatFromCause(PyExc_RuntimeError,
/* Add note to the exception */
PyObject *exc = PyErr_GetRaisedException();
assert(exc != NULL);
PyObject *note = PyUnicode_FromFormat(
"Error calling __set_name__ on '%.100s' instance %R "
"in '%.100s'",
Py_TYPE(value)->tp_name, key, type->tp_name);

if (note) {
int res = _PyException_AddNote(exc, note);
Py_DECREF(note);
if (res == 0) {
PyErr_SetRaisedException(exc);
goto error;
}
}
_PyErr_ChainExceptions1(exc);
goto error;
}
Py_DECREF(res);
else {
Py_DECREF(res);
}
}

Py_DECREF(names_to_set);
Expand Down