Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit d76531a

Browse filesBrowse files
berkerpeksagwebsurfer5
authored andcommitted
bpo-19072: Make @classmethod support chained decorators (pythonGH-8405)
1 parent c472c29 commit d76531a
Copy full SHA for d76531a

File tree

Expand file treeCollapse file tree

5 files changed

+71
-2
lines changed
Filter options
Expand file treeCollapse file tree

5 files changed

+71
-2
lines changed

‎Doc/library/functions.rst

Copy file name to clipboardExpand all lines: Doc/library/functions.rst
+4-2Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,10 +222,12 @@ are always available. They are listed here in alphabetical order.
222222
implied first argument.
223223

224224
Class methods are different than C++ or Java static methods. If you want those,
225-
see :func:`staticmethod`.
226-
225+
see :func:`staticmethod` in this section.
227226
For more information on class methods, see :ref:`types`.
228227

228+
.. versionchanged:: 3.9
229+
Class methods can now wrap other :term:`descriptors <descriptor>` such as
230+
:func:`property`.
229231

230232
.. function:: compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)
231233

‎Lib/test/test_decorators.py

Copy file name to clipboardExpand all lines: Lib/test/test_decorators.py
+39Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,45 @@ def bar(): return 42
265265
self.assertEqual(bar(), 42)
266266
self.assertEqual(actions, expected_actions)
267267

268+
def test_wrapped_descriptor_inside_classmethod(self):
269+
class BoundWrapper:
270+
def __init__(self, wrapped):
271+
self.__wrapped__ = wrapped
272+
273+
def __call__(self, *args, **kwargs):
274+
return self.__wrapped__(*args, **kwargs)
275+
276+
class Wrapper:
277+
def __init__(self, wrapped):
278+
self.__wrapped__ = wrapped
279+
280+
def __get__(self, instance, owner):
281+
bound_function = self.__wrapped__.__get__(instance, owner)
282+
return BoundWrapper(bound_function)
283+
284+
def decorator(wrapped):
285+
return Wrapper(wrapped)
286+
287+
class Class:
288+
@decorator
289+
@classmethod
290+
def inner(cls):
291+
# This should already work.
292+
return 'spam'
293+
294+
@classmethod
295+
@decorator
296+
def outer(cls):
297+
# Raised TypeError with a message saying that the 'Wrapper'
298+
# object is not callable.
299+
return 'eggs'
300+
301+
self.assertEqual(Class.inner(), 'spam')
302+
self.assertEqual(Class.outer(), 'eggs')
303+
self.assertEqual(Class().inner(), 'spam')
304+
self.assertEqual(Class().outer(), 'eggs')
305+
306+
268307
class TestClassDecorators(unittest.TestCase):
269308

270309
def test_simple(self):

‎Lib/test/test_property.py

Copy file name to clipboardExpand all lines: Lib/test/test_property.py
+21Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,27 @@ def test_refleaks_in___init__(self):
183183
fake_prop.__init__('fget', 'fset', 'fdel', 'doc')
184184
self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)
185185

186+
@unittest.skipIf(sys.flags.optimize >= 2,
187+
"Docstrings are omitted with -O2 and above")
188+
def test_class_property(self):
189+
class A:
190+
@classmethod
191+
@property
192+
def __doc__(cls):
193+
return 'A doc for %r' % cls.__name__
194+
self.assertEqual(A.__doc__, "A doc for 'A'")
195+
196+
@unittest.skipIf(sys.flags.optimize >= 2,
197+
"Docstrings are omitted with -O2 and above")
198+
def test_class_property_override(self):
199+
class A:
200+
"""First"""
201+
@classmethod
202+
@property
203+
def __doc__(cls):
204+
return 'Second'
205+
self.assertEqual(A.__doc__, 'Second')
206+
186207

187208
# Issue 5890: subclasses of property do not preserve method __doc__ strings
188209
class PropertySub(property):
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The :class:`classmethod` decorator can now wrap other descriptors
2+
such as property objects. Adapted from a patch written by Graham
3+
Dumpleton.

‎Objects/funcobject.c

Copy file name to clipboardExpand all lines: Objects/funcobject.c
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,10 @@ cm_descr_get(PyObject *self, PyObject *obj, PyObject *type)
741741
}
742742
if (type == NULL)
743743
type = (PyObject *)(Py_TYPE(obj));
744+
if (Py_TYPE(cm->cm_callable)->tp_descr_get != NULL) {
745+
return Py_TYPE(cm->cm_callable)->tp_descr_get(cm->cm_callable, type,
746+
NULL);
747+
}
744748
return PyMethod_New(cm->cm_callable, type);
745749
}
746750

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.