summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/test/test_decorators.py86
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2021-07-13-17-47-32.bpo-42073.9wopiC.rst2
-rw-r--r--Objects/funcobject.c2
3 files changed, 89 insertions, 1 deletions
diff --git a/Lib/test/test_decorators.py b/Lib/test/test_decorators.py
index d435345..57a741f 100644
--- a/Lib/test/test_decorators.py
+++ b/Lib/test/test_decorators.py
@@ -1,5 +1,6 @@
from test import support
import unittest
+from types import MethodType
def funcattrs(**kwds):
def decorate(func):
@@ -329,6 +330,91 @@ class TestDecorators(unittest.TestCase):
self.assertEqual(Class().inner(), 'spam')
self.assertEqual(Class().outer(), 'eggs')
+ def test_wrapped_classmethod_inside_classmethod(self):
+ class MyClassMethod1:
+ def __init__(self, func):
+ self.func = func
+
+ def __call__(self, cls):
+ if hasattr(self.func, '__get__'):
+ return self.func.__get__(cls, cls)()
+ return self.func(cls)
+
+ def __get__(self, instance, owner=None):
+ if owner is None:
+ owner = type(instance)
+ return MethodType(self, owner)
+
+ class MyClassMethod2:
+ def __init__(self, func):
+ if isinstance(func, classmethod):
+ func = func.__func__
+ self.func = func
+
+ def __call__(self, cls):
+ return self.func(cls)
+
+ def __get__(self, instance, owner=None):
+ if owner is None:
+ owner = type(instance)
+ return MethodType(self, owner)
+
+ for myclassmethod in [MyClassMethod1, MyClassMethod2]:
+ class A:
+ @myclassmethod
+ def f1(cls):
+ return cls
+
+ @classmethod
+ @myclassmethod
+ def f2(cls):
+ return cls
+
+ @myclassmethod
+ @classmethod
+ def f3(cls):
+ return cls
+
+ @classmethod
+ @classmethod
+ def f4(cls):
+ return cls
+
+ @myclassmethod
+ @MyClassMethod1
+ def f5(cls):
+ return cls
+
+ @myclassmethod
+ @MyClassMethod2
+ def f6(cls):
+ return cls
+
+ self.assertIs(A.f1(), A)
+ self.assertIs(A.f2(), A)
+ self.assertIs(A.f3(), A)
+ self.assertIs(A.f4(), A)
+ self.assertIs(A.f5(), A)
+ self.assertIs(A.f6(), A)
+ a = A()
+ self.assertIs(a.f1(), A)
+ self.assertIs(a.f2(), A)
+ self.assertIs(a.f3(), A)
+ self.assertIs(a.f4(), A)
+ self.assertIs(a.f5(), A)
+ self.assertIs(a.f6(), A)
+
+ def f(cls):
+ return cls
+
+ self.assertIs(myclassmethod(f).__get__(a)(), A)
+ self.assertIs(myclassmethod(f).__get__(a, A)(), A)
+ self.assertIs(myclassmethod(f).__get__(A, A)(), A)
+ self.assertIs(myclassmethod(f).__get__(A)(), type(A))
+ self.assertIs(classmethod(f).__get__(a)(), A)
+ self.assertIs(classmethod(f).__get__(a, A)(), A)
+ self.assertIs(classmethod(f).__get__(A, A)(), A)
+ self.assertIs(classmethod(f).__get__(A)(), type(A))
class TestClassDecorators(unittest.TestCase):
diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-07-13-17-47-32.bpo-42073.9wopiC.rst b/Misc/NEWS.d/next/Core and Builtins/2021-07-13-17-47-32.bpo-42073.9wopiC.rst
new file mode 100644
index 0000000..988fe67
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2021-07-13-17-47-32.bpo-42073.9wopiC.rst
@@ -0,0 +1,2 @@
+The ``@classmethod`` decorator can now wrap other classmethod-like
+descriptors.
diff --git a/Objects/funcobject.c b/Objects/funcobject.c
index f0b0b67..da648b7 100644
--- a/Objects/funcobject.c
+++ b/Objects/funcobject.c
@@ -825,7 +825,7 @@ cm_descr_get(PyObject *self, PyObject *obj, PyObject *type)
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);
+ type);
}
return PyMethod_New(cm->cm_callable, type);
}