diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst --- a/Doc/whatsnew/3.5.rst +++ b/Doc/whatsnew/3.5.rst @@ -225,16 +225,25 @@ Some smaller changes made to the core Py * Added the ``'namereplace'`` error handlers. The ``'backslashreplace'`` error handlers now works with decoding and translating. (Contributed by Serhiy Storchaka in :issue:`19676` and :issue:`22286`.) * The :option:`-b` option now affects comparisons of :class:`bytes` with :class:`int`. (Contributed by Serhiy Storchaka in :issue:`23681`) +* Property docstrings are now writable. This is especially usable for + :func:`collections.namedtuple` docstrings. You can now easily update + docstrings produced by :func:``collections.namedtuple`:: + + Point = namedtuple('Point', 'x y') + Point.x.__doc__ = 'docstring of x' + + (Contributed by Berker Peksag in :issue:`24064`.) + New Modules =========== .. _whatsnew-zipapp: zipapp ------ @@ -271,16 +280,27 @@ cgi code ---- * The :func:`code.InteractiveInterpreter.showtraceback` method now prints the full chained traceback, just like the interactive interpreter. (Contributed by Claudiu Popa in :issue:`17442`.) +collections +----------- + +* You can now easily update docstrings produced by + :func:``collections.namedtuple`:: + + Point = namedtuple('Point', 'x y') + Point.x.__doc__ = 'docstring of x' + + (Contributed by Berker Peksag in :issue:`24064`.) + compileall ---------- * :func:`compileall.compile_dir` and :mod:`compileall`'s command-line interface can now do parallel bytecode compilation. (Contributed by Claudiu Popa in :issue:`16104`.) contextlib diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -192,16 +192,24 @@ class TestNamedTuple(unittest.TestCase): self.assertRaises(TypeError, Point._make, [11, 22, 33]) # catch too many args @unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") def test_factory_doc_attr(self): Point = namedtuple('Point', 'x y') self.assertEqual(Point.__doc__, 'Point(x, y)') + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def test_doc_writable(self): + Point = namedtuple('Point', 'x y') + self.assertEqual(Point.x.__doc__, 'Alias for field number 0') + Point.x.__doc__ = 'docstring for Point.x' + self.assertEqual(Point.x.__doc__, 'docstring for Point.x') + def test_name_fixer(self): for spec, renamed in [ [('efg', 'g%hi'), ('efg', '_1')], # field with non-alpha char [('abc', 'class'), ('abc', '_1')], # field has keyword [('8efg', '9ghi'), ('_0', '_1')], # field starts with digit [('abc', '_efg'), ('abc', '_1')], # field with leading underscore [('abc', 'efg', 'efg', 'ghi'), ('abc', 'efg', '_2', 'ghi')], # duplicate field [('abc', '', 'x'), ('abc', '_1', 'x')], # fieldname is a space diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -2017,27 +2017,30 @@ order (MRO) for bases """ self.assertIn("fset", attrs) self.assertIn("fdel", attrs) self.assertEqual(raw.__doc__, "I'm the x property.") self.assertIs(raw.fget, C.__dict__['getx']) self.assertIs(raw.fset, C.__dict__['setx']) self.assertIs(raw.fdel, C.__dict__['delx']) - for attr in "__doc__", "fget", "fset", "fdel": + for attr in "fget", "fset", "fdel": try: setattr(raw, attr, 42) except AttributeError as msg: if str(msg).find('readonly') < 0: self.fail("when setting readonly attr %r on a property, " "got unexpected AttributeError msg %r" % (attr, str(msg))) else: self.fail("expected AttributeError from trying to set readonly %r " "attr on a property" % attr) + raw.__doc__ = 42 + self.assertEqual(raw.__doc__, 42) + class D(object): __getitem__ = property(lambda s: 1/0) d = D() try: for i in d: str(i) except ZeroDivisionError: diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -71,16 +71,23 @@ class PropertyNewGetter(object): def spam(self): """original docstring""" return 1 @spam.getter def spam(self): """new docstring""" return 8 +class PropertyWritableDoc(object): + + @property + def spam(self): + """Eggs""" + return "eggs" + class PropertyTests(unittest.TestCase): def test_property_decorator_baseclass(self): # see #1620 base = BaseClass() self.assertEqual(base.spam, 5) self.assertEqual(base._spam, 5) base.spam = 10 self.assertEqual(base.spam, 10) @@ -145,16 +152,31 @@ class PropertyTests(unittest.TestCase): with self.assertRaises(ValueError): class C(object): def foo(self): pass foo.__isabstractmethod__ = NotBool() foo = property(foo) C.foo.__isabstractmethod__ + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def test_property_builtin_doc_writable(self): + p = property(doc='basic') + self.assertEqual(p.__doc__, 'basic') + p.__doc__ = 'extended' + self.assertEqual(p.__doc__, 'extended') + + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def test_property_decorator_doc_writable(self): + sub = PropertyWritableDoc() + self.assertEqual(sub.__class__.spam.__doc__, 'Eggs') + sub.__class__.spam.__doc__ = 'Spam' + self.assertEqual(sub.__class__.spam.__doc__, 'Spam') # 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__ = () diff --git a/Objects/descrobject.c b/Objects/descrobject.c --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -1308,17 +1308,17 @@ typedef struct { static PyObject * property_copy(PyObject *, PyObject *, PyObject *, PyObject *); static PyMemberDef property_members[] = { {"fget", T_OBJECT, offsetof(propertyobject, prop_get), READONLY}, {"fset", T_OBJECT, offsetof(propertyobject, prop_set), READONLY}, {"fdel", T_OBJECT, offsetof(propertyobject, prop_del), READONLY}, - {"__doc__", T_OBJECT, offsetof(propertyobject, prop_doc), READONLY}, + {"__doc__", T_OBJECT, offsetof(propertyobject, prop_doc), NULL}, {0} }; PyDoc_STRVAR(getter_doc, "Descriptor to change the getter on a property."); static PyObject *