mirror of
https://github.com/python/cpython
synced 2024-10-14 12:58:03 +00:00
bpo-19072: Make @classmethod support chained decorators (GH-8405)
This commit is contained in:
parent
0dfc025ccc
commit
805f8f9afe
|
@ -222,10 +222,12 @@ are always available. They are listed here in alphabetical order.
|
||||||
implied first argument.
|
implied first argument.
|
||||||
|
|
||||||
Class methods are different than C++ or Java static methods. If you want those,
|
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`.
|
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)
|
.. function:: compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)
|
||||||
|
|
||||||
|
|
|
@ -265,6 +265,45 @@ def bar(): return 42
|
||||||
self.assertEqual(bar(), 42)
|
self.assertEqual(bar(), 42)
|
||||||
self.assertEqual(actions, expected_actions)
|
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):
|
class TestClassDecorators(unittest.TestCase):
|
||||||
|
|
||||||
def test_simple(self):
|
def test_simple(self):
|
||||||
|
|
|
@ -183,6 +183,27 @@ def test_refleaks_in___init__(self):
|
||||||
fake_prop.__init__('fget', 'fset', 'fdel', 'doc')
|
fake_prop.__init__('fget', 'fset', 'fdel', 'doc')
|
||||||
self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)
|
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
|
# Issue 5890: subclasses of property do not preserve method __doc__ strings
|
||||||
class PropertySub(property):
|
class PropertySub(property):
|
||||||
|
|
|
@ -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.
|
|
@ -741,6 +741,10 @@ cm_descr_get(PyObject *self, PyObject *obj, PyObject *type)
|
||||||
}
|
}
|
||||||
if (type == NULL)
|
if (type == NULL)
|
||||||
type = (PyObject *)(Py_TYPE(obj));
|
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);
|
return PyMethod_New(cm->cm_callable, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue