]> granicus.if.org Git - python/commitdiff
Merged revisions 72299 via svnmerge from
authorR. David Murray <rdmurray@bitdance.com>
Mon, 4 May 2009 22:59:07 +0000 (22:59 +0000)
committerR. David Murray <rdmurray@bitdance.com>
Mon, 4 May 2009 22:59:07 +0000 (22:59 +0000)
svn+ssh://pythondev@svn.python.org/python/trunk

........
  r72299 | r.david.murray | 2009-05-04 18:16:24 -0400 (Mon, 04 May 2009) | 7 lines

  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.
........

Lib/test/test_property.py
Misc/NEWS
Objects/descrobject.c

index c957ec272c1dadb53e2f4bae2ba9554adf3090dd..4ce1da2b69a09bb701db255dd3d5a7963ca7c7e4 100644 (file)
@@ -60,6 +60,22 @@ class PropertyDocSub(PropertyDocBase):
         """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 @@ class PropertyTests(unittest.TestCase):
         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()
index 81adc944bba75da83415c98b3f95f9542fa09cb3..a384c41e7c18edee51e756536ae2956847196cc3 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -12,6 +12,10 @@ What's New in Python 3.1 beta 1?
 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.
 
index cfd4b4fc7017851bb0613addf791c3c689c4d586..9715238ff76f022621773c775082f26664d643bf 100644 (file)
@@ -1246,25 +1246,19 @@ property_copy(PyObject *old, PyObject *get, PyObject *set, PyObject *del,
        }
        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;
 }
 
@@ -1301,8 +1295,21 @@ property_init(PyObject *self, PyObject *args, PyObject *kwds)
        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();