diff options
author | R. David Murray <rdmurray@bitdance.com> | 2009-05-04 22:59:07 (GMT) |
---|---|---|
committer | R. David Murray <rdmurray@bitdance.com> | 2009-05-04 22:59:07 (GMT) |
commit | b18500d39d791c879e9904ebac293402b4a7cd34 (patch) | |
tree | 109aad772fed30511f12977cc4b10b9b3fd56722 | |
parent | e04b627a116b4fed8fd76f9376ef53592c49d183 (diff) | |
download | cpython-b18500d39d791c879e9904ebac293402b4a7cd34.zip cpython-b18500d39d791c879e9904ebac293402b4a7cd34.tar.gz cpython-b18500d39d791c879e9904ebac293402b4a7cd34.tar.bz2 |
Merged revisions 72299 via svnmerge from
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.
........
-rw-r--r-- | Lib/test/test_property.py | 116 | ||||
-rw-r--r-- | Misc/NEWS | 4 | ||||
-rw-r--r-- | Objects/descrobject.c | 39 |
3 files changed, 142 insertions, 17 deletions
diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index c957ec2..4ce1da2 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -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() @@ -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. diff --git a/Objects/descrobject.c b/Objects/descrobject.c index cfd4b4f..9715238 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -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(); |