summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/functions.rst6
-rw-r--r--Lib/test/test_decorators.py39
-rw-r--r--Lib/test/test_property.py21
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2018-07-23-13-09-54.bpo-19072.Gc59GS.rst3
-rw-r--r--Objects/funcobject.c4
5 files changed, 71 insertions, 2 deletions
diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst
index c225f3d..a7b6610 100644
--- a/Doc/library/functions.rst
+++ b/Doc/library/functions.rst
@@ -222,10 +222,12 @@ are always available. They are listed here in alphabetical order.
implied first argument.
Class methods are different than C++ or Java static methods. If you want those,
- see :func:`staticmethod`.
-
+ see :func:`staticmethod` in this section.
For more information on class methods, see :ref:`types`.
+ .. versionchanged:: 3.9
+ Class methods can now wrap other :term:`descriptors <descriptor>` such as
+ :func:`property`.
.. function:: compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)
diff --git a/Lib/test/test_decorators.py b/Lib/test/test_decorators.py
index d0a2ec9..8953f64 100644
--- a/Lib/test/test_decorators.py
+++ b/Lib/test/test_decorators.py
@@ -265,6 +265,45 @@ class TestDecorators(unittest.TestCase):
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 f6f8f5e..172737a 100644
--- a/Lib/test/test_property.py
+++ b/Lib/test/test_property.py
@@ -183,6 +183,27 @@ class PropertyTests(unittest.TestCase):
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):
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 0000000..1d27789
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2018-07-23-13-09-54.bpo-19072.Gc59GS.rst
@@ -0,0 +1,3 @@
+The :class:`classmethod` decorator can now wrap other descriptors
+such as property objects. Adapted from a patch written by Graham
+Dumpleton.
diff --git a/Objects/funcobject.c b/Objects/funcobject.c
index a65c1f4..b6ffc2a 100644
--- a/Objects/funcobject.c
+++ b/Objects/funcobject.c
@@ -741,6 +741,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);
}