From 16894593ceb45bd98d1cf6a59db5b83a5eed8fee Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Mon, 23 Jul 2018 13:28:42 +0300 Subject: [PATCH 1/5] bpo-19072: Make @classmethod support chained decorators --- Lib/test/test_decorators.py | 39 +++++++++++++++++++ Lib/test/test_property.py | 22 ++++++++++- .../2018-07-23-13-09-54.bpo-19072.Gc59GS.rst | 2 + Objects/funcobject.c | 4 ++ 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2018-07-23-13-09-54.bpo-19072.Gc59GS.rst diff --git a/Lib/test/test_decorators.py b/Lib/test/test_decorators.py index d0a2ec9fddb866..8953f648061225 100644 --- a/Lib/test/test_decorators.py +++ b/Lib/test/test_decorators.py @@ -265,6 +265,45 @@ def bar(): return 42 self.assertEqual(bar(), 42) self.assertEqual(actions, expected_actions) + def test_wrapped_descriptor_inside_classmethod(self): + class BoundWrapper: + def __init__(self, wrapped): + self.__wrapped__ = wrapped + + def __call__(self, *args, **kwargs): + return self.__wrapped__(*args, **kwargs) + + class Wrapper: + def __init__(self, wrapped): + self.__wrapped__ = wrapped + + def __get__(self, instance, owner): + bound_function = self.__wrapped__.__get__(instance, owner) + return BoundWrapper(bound_function) + + def decorator(wrapped): + return Wrapper(wrapped) + + class Class: + @decorator + @classmethod + def inner(cls): + # This should already work. + return 'spam' + + @classmethod + @decorator + def outer(cls): + # Raised TypeError with a message saying that the 'Wrapper' + # object is not callable. + return 'eggs' + + self.assertEqual(Class.inner(), 'spam') + self.assertEqual(Class.outer(), 'eggs') + self.assertEqual(Class().inner(), 'spam') + self.assertEqual(Class().outer(), 'eggs') + + class TestClassDecorators(unittest.TestCase): def test_simple(self): diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index f6f8f5ed0e45ee..8a76815e0cd722 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -183,6 +183,27 @@ def test_refleaks_in___init__(self): fake_prop.__init__('fget', 'fset', 'fdel', 'doc') self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10) + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def test_class_property(self): + class A: + @classmethod + @property + def __doc__(cls): + return 'A doc for %r' % cls.__name__ + self.assertEqual(A.__doc__, "A doc for 'A'") + + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def test_class_property_override(self): + class A: + """First""" + @classmethod + @property + def __doc__(cls): + return 'Second' + self.assertEqual(A.__doc__, 'Second') + # Issue 5890: subclasses of property do not preserve method __doc__ strings class PropertySub(property): @@ -278,6 +299,5 @@ def spam(self): self.assertEqual(Foo.spam.__doc__, "a new docstring") - if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-07-23-13-09-54.bpo-19072.Gc59GS.rst b/Misc/NEWS.d/next/Core and Builtins/2018-07-23-13-09-54.bpo-19072.Gc59GS.rst new file mode 100644 index 00000000000000..b5e8e0e5f9eaa6 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-07-23-13-09-54.bpo-19072.Gc59GS.rst @@ -0,0 +1,2 @@ +The :class:`classmethod` decorator now works correctly when it was wrapped by +a chained decorator. Adapted from a patch written by Graham Dumpleton. diff --git a/Objects/funcobject.c b/Objects/funcobject.c index c2f79c05dbb21b..08f8426476f50a 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -703,6 +703,10 @@ cm_descr_get(PyObject *self, PyObject *obj, PyObject *type) } if (type == NULL) type = (PyObject *)(Py_TYPE(obj)); + if (Py_TYPE(cm->cm_callable)->tp_descr_get != NULL) { + return Py_TYPE(cm->cm_callable)->tp_descr_get(cm->cm_callable, type, + NULL); + } return PyMethod_New(cm->cm_callable, type); } From 8402e69bd249f2978c08b39d9dae42c11fafb055 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Mon, 23 Jul 2018 13:30:14 +0300 Subject: [PATCH 2/5] add newline back --- Lib/test/test_property.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index 8a76815e0cd722..172737ade143fa 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -299,5 +299,6 @@ def spam(self): self.assertEqual(Foo.spam.__doc__, "a new docstring") + if __name__ == '__main__': unittest.main() From 92b66506be1a3acecbf6773ec15d8b556a03d184 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sat, 24 Aug 2019 12:49:17 -0700 Subject: [PATCH 3/5] Fix wording in NEWS entry --- .../2018-07-23-13-09-54.bpo-19072.Gc59GS.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-07-23-13-09-54.bpo-19072.Gc59GS.rst b/Misc/NEWS.d/next/Core and Builtins/2018-07-23-13-09-54.bpo-19072.Gc59GS.rst index b5e8e0e5f9eaa6..1d27789420e044 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2018-07-23-13-09-54.bpo-19072.Gc59GS.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2018-07-23-13-09-54.bpo-19072.Gc59GS.rst @@ -1,2 +1,3 @@ -The :class:`classmethod` decorator now works correctly when it was wrapped by -a chained decorator. Adapted from a patch written by Graham Dumpleton. +The :class:`classmethod` decorator can now wrap other descriptors +such as property objects. Adapted from a patch written by Graham +Dumpleton. From a5ce586ba2cfe34ef4f952f135138b63078ee03c Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sat, 24 Aug 2019 14:26:32 -0700 Subject: [PATCH 4/5] Add documentation --- Doc/library/functions.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index 68a5dce5d09b72..57e3860c8c33a1 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -219,10 +219,12 @@ are always available. They are listed here in alphabetical order. Class methods are different than C++ or Java static methods. If you want those, see :func:`staticmethod` in this section. - For more information on class methods, consult the documentation on the standard type hierarchy in :ref:`types`. + .. versionchanged:: 3.9 + Class methods can now wrap other :term:`descriptors ` such as + :func:`property`. .. function:: compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1) From 4d3e4131b9cc4e4ed5334019f9b461c9ba1b0cf0 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sat, 24 Aug 2019 15:10:58 -0700 Subject: [PATCH 5/5] Fix markup --- Doc/library/functions.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index 98db71c2c6def5..a7b6610ebdf1ed 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -226,8 +226,8 @@ are always available. They are listed here in alphabetical order. For more information on class methods, see :ref:`types`. .. versionchanged:: 3.9 - Class methods can now wrap other :term:`descriptors ` such as - :func:`property`. + Class methods can now wrap other :term:`descriptors ` such as + :func:`property`. .. function:: compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)