Skip to content

Commit f530eaf

Browse files
committed
pythongh-98963: Add a note to the error for property subclasses without __doc__
Subclasses created using the C API don't get __dict__, which means their __doc__ can't be set for each property separately. The __doc__ descriptor from `property` itself is shadowed by the subclass's docstring. This edge case isn't really worth mentioning in the docs. This adds a note that should guide anyone encountering the issue to the right fix.
1 parent 780757a commit f530eaf

3 files changed

Lines changed: 35 additions & 1 deletion

File tree

‎Lib/test/test_descr.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2359,6 +2359,17 @@ def test_testcapi_no_segfault(self):
23592359
class X(object):
23602360
p = property(_testcapi.test_with_docstring)
23612361

2362+
def test_subclass_with_nonwritable_doc(self):
2363+
# gh-98963: subclasses must have writable __doc__. A note is added to
2364+
# the exception in this surprising edge case.
2365+
class BadProperty(property):
2366+
__doc__ = property(lambda: None)
2367+
with self.assertRaises(AttributeError) as cm:
2368+
p = BadProperty(lambda: 123)
2369+
notes = cm.exception.__notes__
2370+
wanted = "subclasses of 'property' need to provide a writable __doc__"
2371+
self.assertTrue(any(note.startswith(wanted) for note in notes), notes)
2372+
23622373
def test_properties_plus(self):
23632374
class C(object):
23642375
foo = property(doc="hello")
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add a note to the ``AttributeError`` raised when instantiating a subclass of
2+
:py:type:`property` that does not have a writable ``__doc__`` attribute.
3+
(Creating such a subclass is possible via the C API.)

‎Objects/descrobject.c‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1826,8 +1826,28 @@ property_init_impl(propertyobject *self, PyObject *fget, PyObject *fset,
18261826
int err = PyObject_SetAttr(
18271827
(PyObject *)self, &_Py_ID(__doc__), prop_doc);
18281828
Py_XDECREF(prop_doc);
1829-
if (err < 0)
1829+
if (err < 0) {
1830+
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
1831+
// gh-98963: the subclass doesn't have a __dict__
1832+
// (probably it was subclassed using C API).
1833+
// Add a note to prevent surprises.
1834+
PyObject *type, *value, *traceback;
1835+
PyErr_Fetch(&type, &value, &traceback);
1836+
PyErr_NormalizeException(&type, &value, &traceback);
1837+
if (value) {
1838+
PyObject_CallMethod(
1839+
value, "add_note", "s",
1840+
"subclasses of 'property' need to provide a writable "
1841+
"__doc__ attribute (e.g. a __doc__ member or a "
1842+
"__dict__) to avoid per-property docstrings being "
1843+
"shadowed by the subclass docstring");
1844+
// ignore errors while setting the note
1845+
PyErr_Clear();
1846+
}
1847+
PyErr_Restore(type, value, traceback);
1848+
}
18301849
return -1;
1850+
}
18311851
}
18321852

18331853
return 0;

0 commit comments

Comments
 (0)