Skip to content

Commit f386cc9

Browse files
miss-islingtonembrayserhiy-storchaka
authored
[3.13] bpo-24766: doc= argument to subclasses of property not handled correctly (GH-2487) (GH-120305)
(cherry picked from commit 4829522) Co-authored-by: E. M. Bray <[email protected]> Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent 7993268 commit f386cc9

File tree

3 files changed

+39
-15
lines changed

3 files changed

+39
-15
lines changed

Lib/test/test_property.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,40 @@ def getter3(self):
463463
self.assertEqual(p.__doc__, "user")
464464
self.assertEqual(p2.__doc__, "user")
465465

466+
@unittest.skipIf(sys.flags.optimize >= 2,
467+
"Docstrings are omitted with -O2 and above")
468+
def test_prefer_explicit_doc(self):
469+
# Issue 25757: subclasses of property lose docstring
470+
self.assertEqual(property(doc="explicit doc").__doc__, "explicit doc")
471+
self.assertEqual(PropertySub(doc="explicit doc").__doc__, "explicit doc")
472+
473+
class Foo:
474+
spam = PropertySub(doc="spam explicit doc")
475+
476+
@spam.getter
477+
def spam(self):
478+
"""ignored as doc already set"""
479+
return 1
480+
481+
def _stuff_getter(self):
482+
"""ignored as doc set directly"""
483+
stuff = PropertySub(doc="stuff doc argument", fget=_stuff_getter)
484+
485+
#self.assertEqual(Foo.spam.__doc__, "spam explicit doc")
486+
self.assertEqual(Foo.stuff.__doc__, "stuff doc argument")
487+
488+
def test_property_no_doc_on_getter(self):
489+
# If a property's getter has no __doc__ then the property's doc should
490+
# be None; test that this is consistent with subclasses as well; see
491+
# GH-2487
492+
class NoDoc:
493+
@property
494+
def __doc__(self):
495+
raise AttributeError
496+
497+
self.assertEqual(property(NoDoc()).__doc__, None)
498+
self.assertEqual(PropertySub(NoDoc()).__doc__, None)
499+
466500
@unittest.skipIf(sys.flags.optimize >= 2,
467501
"Docstrings are omitted with -O2 and above")
468502
def test_property_setter_copies_getter_docstring(self):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix handling of ``doc`` argument to subclasses of ``property``.

Objects/descrobject.c

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1859,22 +1859,9 @@ property_init_impl(propertyobject *self, PyObject *fget, PyObject *fset,
18591859
/* if no docstring given and the getter has one, use that one */
18601860
else if (fget != NULL) {
18611861
int rc = PyObject_GetOptionalAttr(fget, &_Py_ID(__doc__), &prop_doc);
1862-
if (rc <= 0) {
1862+
if (rc < 0) {
18631863
return rc;
18641864
}
1865-
if (!Py_IS_TYPE(self, &PyProperty_Type) &&
1866-
prop_doc != NULL && prop_doc != Py_None) {
1867-
// This oddity preserves the long existing behavior of surfacing
1868-
// an AttributeError when using a dict-less (__slots__) property
1869-
// subclass as a decorator on a getter method with a docstring.
1870-
// See PropertySubclassTest.test_slots_docstring_copy_exception.
1871-
int err = PyObject_SetAttr(
1872-
(PyObject *)self, &_Py_ID(__doc__), prop_doc);
1873-
if (err < 0) {
1874-
Py_DECREF(prop_doc); // release our new reference.
1875-
return -1;
1876-
}
1877-
}
18781865
if (prop_doc == Py_None) {
18791866
prop_doc = NULL;
18801867
Py_DECREF(Py_None);
@@ -1902,7 +1889,9 @@ property_init_impl(propertyobject *self, PyObject *fget, PyObject *fset,
19021889
Py_DECREF(prop_doc);
19031890
if (err < 0) {
19041891
assert(PyErr_Occurred());
1905-
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
1892+
if (!self->getter_doc &&
1893+
PyErr_ExceptionMatches(PyExc_AttributeError))
1894+
{
19061895
PyErr_Clear();
19071896
// https://github.com/python/cpython/issues/98963#issuecomment-1574413319
19081897
// Python silently dropped this doc assignment through 3.11.

0 commit comments

Comments
 (0)