[Python-checkins] r72299 - in python/trunk: Lib/test/test_property.py Misc/NEWS Objects/descrobject.c

r.david.murray python-checkins at python.org
Tue May 5 00:16:24 CEST 2009


Author: r.david.murray
Date: Tue May  5 00:16:24 2009
New Revision: 72299

Log:
Fix issue 5890: (property subclass shadows __doc__ string) by inserting
the __doc__ into the subclass instance __dict__.  The fix refactors
property_copy to call property_init in such a way that the __doc__
logic is re-executed correctly when getter_doc is 1, thus simplifying
property_copy.



Modified:
   python/trunk/Lib/test/test_property.py
   python/trunk/Misc/NEWS
   python/trunk/Objects/descrobject.c

Modified: python/trunk/Lib/test/test_property.py
==============================================================================
--- python/trunk/Lib/test/test_property.py	(original)
+++ python/trunk/Lib/test/test_property.py	Tue May  5 00:16:24 2009
@@ -60,6 +60,22 @@
         """The decorator does not use this doc string"""
         return self._spam
 
+class PropertySubNewGetter(BaseClass):
+    @BaseClass.spam.getter
+    def spam(self):
+        """new docstring"""
+        return 5
+
+class PropertyNewGetter(object):
+    @property
+    def spam(self):
+        """original docstring"""
+        return 1
+    @spam.getter
+    def spam(self):
+        """new docstring"""
+        return 8
+
 class PropertyTests(unittest.TestCase):
     def test_property_decorator_baseclass(self):
         # see #1620
@@ -91,8 +107,106 @@
         self.assertEqual(base.__class__.spam.__doc__, "spam spam spam")
         self.assertEqual(sub.__class__.spam.__doc__, "spam spam spam")
 
+    def test_property_getter_doc_override(self):
+        newgettersub = PropertySubNewGetter()
+        self.assertEqual(newgettersub.spam, 5)
+        self.assertEqual(newgettersub.__class__.spam.__doc__, "new docstring")
+        newgetter = PropertyNewGetter()
+        self.assertEqual(newgetter.spam, 8)
+        self.assertEqual(newgetter.__class__.spam.__doc__, "new docstring")
+
+
+# Issue 5890: subclasses of property do not preserve method __doc__ strings
+class PropertySub(property):
+    """This is a subclass of property"""
+
+class PropertySubSlots(property):
+    """This is a subclass of property that defines __slots__"""
+    __slots__ = ()
+
+class PropertySubclassTests(unittest.TestCase):
+
+    def test_docstring_copy(self):
+        class Foo(object):
+            @PropertySub
+            def spam(self):
+                """spam wrapped in property subclass"""
+                return 1
+        self.assertEqual(
+            Foo.spam.__doc__,
+            "spam wrapped in property subclass")
+
+    def test_slots_docstring_copy_exception(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_setter_copies_getter_docstring(self):
+        class Foo(object):
+            def __init__(self): self._spam = 1
+            @PropertySub
+            def spam(self):
+                """spam wrapped in property subclass"""
+                return self._spam
+            @spam.setter
+            def spam(self, value):
+                """this docstring is ignored"""
+                self._spam = value
+        foo = Foo()
+        self.assertEqual(foo.spam, 1)
+        foo.spam = 2
+        self.assertEqual(foo.spam, 2)
+        self.assertEqual(
+            Foo.spam.__doc__,
+            "spam wrapped in property subclass")
+        class FooSub(Foo):
+            @Foo.spam.setter
+            def spam(self, value):
+                """another ignored docstring"""
+                self._spam = 'eggs'
+        foosub = FooSub()
+        self.assertEqual(foosub.spam, 1)
+        foosub.spam = 7
+        self.assertEqual(foosub.spam, 'eggs')
+        self.assertEqual(
+            FooSub.spam.__doc__,
+            "spam wrapped in property subclass")
+
+    def test_property_new_getter_new_docstring(self):
+
+        class Foo(object):
+            @PropertySub
+            def spam(self):
+                """a docstring"""
+                return 1
+            @spam.getter
+            def spam(self):
+                """a new docstring"""
+                return 2
+        self.assertEqual(Foo.spam.__doc__, "a new docstring")
+        class FooBase(object):
+            @PropertySub
+            def spam(self):
+                """a docstring"""
+                return 1
+        class Foo2(FooBase):
+            @FooBase.spam.getter
+            def spam(self):
+                """a new docstring"""
+                return 2
+        self.assertEqual(Foo.spam.__doc__, "a new docstring")
+
+
+
 def test_main():
-    run_unittest(PropertyTests)
+    run_unittest(PropertyTests, PropertySubclassTests)
 
 if __name__ == '__main__':
     test_main()

Modified: python/trunk/Misc/NEWS
==============================================================================
--- python/trunk/Misc/NEWS	(original)
+++ python/trunk/Misc/NEWS	Tue May  5 00:16:24 2009
@@ -12,6 +12,10 @@
 Core and Builtins
 -----------------
 
+- Issue #5890: in subclasses of 'property' the __doc__ attribute was
+  shadowed by classtype's, even if it was None.  property now
+  inserts the __doc__ into the subclass instance __dict__.
+
 - Issue #4426: The UTF-7 decoder was too strict and didn't accept some legal
   sequences. Patch by Nick Barnes and Victor Stinner.
 

Modified: python/trunk/Objects/descrobject.c
==============================================================================
--- python/trunk/Objects/descrobject.c	(original)
+++ python/trunk/Objects/descrobject.c	Tue May  5 00:16:24 2009
@@ -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,21 @@
 	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;
+	                        }
+                                Py_DECREF(get_doc);
+			}
 			prop->getter_doc = 1;
 		} else {
 			PyErr_Clear();


More information about the Python-checkins mailing list