8000 Merge pull request #524 from python/main · sthagen/python-cpython@7d7d18d · GitHub
[go: up one dir, main page]

Skip to content

Commit 7d7d18d

Browse files
authored
Merge pull request #524 from python/main
bpo-42073: allow classmethod to wrap other classmethod-like descripto…
2 parents 230d06b + b83861f commit 7d7d18d

File tree

3 files changed

+89
-1
lines changed

3 files changed

+89
-1
lines changed

Lib/test/test_decorators.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from test import support
22
import unittest
3+
from types import MethodType
34

45
def funcattrs(**kwds):
56
def decorate(func):
@@ -329,6 +330,91 @@ def outer(cls):
329330
self.assertEqual(Class().inner(), 'spam')
330331
self.assertEqual(Class().outer(), 'eggs')
331332

333+
def test_wrapped_classmethod_inside_classmethod(self):
334+
class MyClassMethod1:
335+
def __init__(self, func):
336+
self.func = func
337+
338+
def __call__(self, cls):
339+
if hasattr(self.func, '__get__'):
340+
return self.func.__get__(cls, cls)()
341+
return self.func(cls)
342+
343+
def __get__(self, instance, owner=None):
344+
if owner is None:
345+
owner = type(instance)
346+
return MethodType(self, owner)
347+
348+
class MyClassMethod2:
349+
def __init__(self, func):
350+
if isinstance(func, classmethod):
351+
func = func.__func__
352+
self.func = func
353+
354+
def __call__(self, cls):
355+
return self.func(cls)
356+
357+
def __get__(self, instance, owner=None):
358+
if owner is None:
359+
owner = type(instance)
360+
return MethodType(self, owner)
361+
362+
for myclassmethod in [MyClassMethod1, MyClassMethod2]:
363+
class A:
364+
@myclassmethod
365+
def f1(cls):
366+
return cls
367+
368+
@classmethod
369+
@myclassmethod
370+
def f2(cls):
371+
return cls
372+
373+
@myclassmethod
374+
@classmethod
375+
def f3(cls):
376+
return cls
377+
378+
@classmethod
379+
@classmethod
380+
def f4(cls):
381+
return cls
382+
383+
@myclassmethod
384+
@MyClassMethod1
385+
def f5(cls):
386+
return cls
387+
388+
@myclassmethod
389+
@MyClassMethod2
390+
def f6(cls):
391+
return cls
392+
393+
self.assertIs(A.f1(), A)
394+
self.assertIs(A.f2(), A)
395+
self.assertIs(A.f3(), A)
396+
self.assertIs(A.f4(), A)
397+
self.assertIs(A.f5(), A)
398+
self.assertIs(A.f6(), A)
399+
a = A()
400+
self.assertIs(a.f1(), A)
401+
self.assertIs(a.f2(), A)
402+
self.assertIs(a.f3(), A)
403+
self.assertIs(a.f4(), A)
404+
self.assertIs(a.f5(), A)
405+
self.assertIs(a.f6(), A)
406+
407+
def f(cls):
408+
return cls
409+
410+
self.assertIs(myclassmethod(f).__get__(a)(), A)
411+
self.assertIs(myclassmethod(f).__get__(a, A)(), A)
412+
self.assertIs(myclassmethod(f).__get__(A, A)(), A)
413+
self.assertIs(myclassmethod(f).__get__(A)(), type(A))
414+
self.assertIs(classmethod(f).__get__(a)(), A)
415+
self.assertIs(classmethod(f).__get__(a, A)(), A)
416+
self.assertIs(classmethod(f).__get__(A, A)(), A)
417+
self.assertIs(classmethod(f).__get__(A)(), type(A))
332418

333419
class TestClassDecorators(unittest.TestCase):
334420

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The ``@classmethod`` decorator can now wrap other classmethod-like
2+
descriptors.

Objects/funcobject.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -851,7 +851,7 @@ cm_descr_get(PyObject *self, PyObject *obj, PyObject *type)
851851
type = (PyObject *)(Py_TYPE(obj));
852852
if (Py_TYPE(cm->cm_callable)->tp_descr_get != NULL) {
853853
return Py_TYPE(cm->cm_callable)->tp_descr_get(cm->cm_callable, type,
854-
NULL);
854+
type);
855855
}
856856
return PyMethod_New(cm->cm_callable, type);
857857
}

0 commit comments

Comments
 (0)
0