diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index d07b8632aa8722..7fe676c1ecc698 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -229,16 +229,18 @@ class PropertySubSlots(property): class PropertySubclassTests(unittest.TestCase): def test_slots_docstring_copy_exception(self): - try: + with self.assertRaises(AttributeError) as cm: class Foo(object): @PropertySubSlots def spam(self): """Trying to copy this docstring will raise an exception""" return 1 - except AttributeError: - pass - else: - raise Exception("AttributeError not raised") + # gh-98963: A note is added to the exception when we don't have + # a writable __doc__. + notes = cm.exception.__notes__ + wanted = "subclasses of 'property' need to provide a writable __doc__" + self.assertTrue(any(note.startswith(wanted) for note in notes), notes) + @unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-11-03-13-54-54.gh-issue-98963.Z3c6M5.rst b/Misc/NEWS.d/next/Core and Builtins/2022-11-03-13-54-54.gh-issue-98963.Z3c6M5.rst new file mode 100644 index 00000000000000..9045b46a9d35d3 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-11-03-13-54-54.gh-issue-98963.Z3c6M5.rst @@ -0,0 +1,3 @@ +Add a note to the ``AttributeError`` raised when instantiating a subclass of +:py:class:`property` that does not have a writable ``__doc__`` attribute. +(Creating such a subclass is possible via the C API.) diff --git a/Objects/descrobject.c b/Objects/descrobject.c index a2974f91aaaec3..ea57029b8963e0 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -1826,8 +1826,28 @@ property_init_impl(propertyobject *self, PyObject *fget, PyObject *fset, int err = PyObject_SetAttr( (PyObject *)self, &_Py_ID(__doc__), prop_doc); Py_XDECREF(prop_doc); - if (err < 0) + if (err < 0) { + if (PyErr_ExceptionMatches(PyExc_AttributeError)) { + // gh-98963: the subclass doesn't have a __dict__ + // (probably it was subclassed using C API). + // Add a note to prevent surprises. + PyObject *type, *value, *traceback; + PyErr_Fetch(&type, &value, &traceback); + PyErr_NormalizeException(&type, &value, &traceback); + if (value) { + PyObject_CallMethod( + value, "add_note", "s", + "subclasses of 'property' need to provide a writable " + "__doc__ attribute (e.g. a __doc__ member or a " + "__dict__) to avoid per-property docstrings being " + "shadowed by the subclass docstring"); + // ignore errors while setting the note + PyErr_Clear(); + } + PyErr_Restore(type, value, traceback); + } return -1; + } } return 0;