Index: Objects/descrobject.c =================================================================== --- Objects/descrobject.c (revision 72178) +++ Objects/descrobject.c (working copy) @@ -1233,25 +1233,19 @@ } if (doc == NULL || doc == Py_None) { Py_XDECREF(doc); - doc = pold->prop_doc ? pold->prop_doc : Py_None; + if (pold->getter_doc && get != Py_None) { + /* make _init use __doc__ from getter */ + doc = Py_None; + } + else { + doc = pold->prop_doc ? pold->prop_doc : Py_None; + } } - + new = PyObject_CallFunction(type, "OOOO", get, set, del, doc); Py_DECREF(type); if (new == NULL) return NULL; - pnew = (propertyobject *)new; - - if (pold->getter_doc && get != Py_None) { - PyObject *get_doc = PyObject_GetAttrString(get, "__doc__"); - if (get_doc != NULL) { - Py_XDECREF(pnew->prop_doc); - pnew->prop_doc = get_doc; /* get_doc already INCREF'd by GetAttr */ - pnew->getter_doc = 1; - } else { - PyErr_Clear(); - } - } return new; } @@ -1288,8 +1282,20 @@ if ((doc == NULL || doc == Py_None) && get != NULL) { PyObject *get_doc = PyObject_GetAttrString(get, "__doc__"); if (get_doc != NULL) { - Py_XDECREF(prop->prop_doc); - prop->prop_doc = get_doc; /* get_doc already INCREF'd by GetAttr */ + /* get_doc already INCREF'd by GetAttr */ + if (Py_TYPE(self)==&PyProperty_Type) { + Py_XDECREF(prop->prop_doc); + prop->prop_doc = get_doc; + } else { + /* Put __doc__ in dict of the subclass instance instead, + otherwise it gets shadowed by class's __doc__. */ + if (PyObject_SetAttrString(self, "__doc__", get_doc) != 0) + { + /* DECREF for props handled by _dealloc */ + Py_DECREF(get_doc); + return -1; + } + } prop->getter_doc = 1; } else { PyErr_Clear(); Index: Lib/test/test_property.py =================================================================== --- Lib/test/test_property.py (revision 72149) +++ Lib/test/test_property.py (working copy) @@ -91,8 +91,62 @@ self.assertEqual(base.__class__.spam.__doc__, "spam spam spam") self.assertEqual(sub.__class__.spam.__doc__, "spam spam spam") + +# Issue 5890: subclasses of property do not preserve method __doc__ strings +class PropertySub(property): + """This is a subclass of property""" + +class PSBaseClass(object): + @PropertySub + def spam(self): + """spam wrapped in a property subclass""" + return 1 + +class PropertySubSlots(property): + """This is a subclass of property that defines __slots__""" + __slots__ = () + +class PSBaseClassWithSetter(object): + @PropertySub + def spam(self): + """a docstring""" + return 1 + @spam.setter + def setspam(self, value): + """set spam, badly""" + x = foo + + +class PropertySubclassTests(unittest.TestCase): + + def test_docstring(self): + base = PSBaseClass() + self.assertEqual( + base.__class__.spam.__doc__, + "spam wrapped in a property subclass") + + def test_slotsdocstringexception(self): + try: + 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") + + def test_property_copy_on_subclass(self): + base = PSBaseClassWithSetter() + self.assertEqual( + base.__class__.spam.__doc__, + "a docstring") + + + def test_main(): - run_unittest(PropertyTests) + run_unittest(PropertyTests, PropertySubclassTests) if __name__ == '__main__': test_main()