Movatterモバイル変換


[0]ホーム

URL:


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

Sign up
Appearance settings

Commit2ce8af3

Browse files
bpo-42073: allow classmethod to wrap other classmethod-like descriptors (GH-27115) (GH-27162)
Patch by Erik Welch.bpo-19072 (GH-8405) allows `classmethod` to wrap other descriptors, but this doesnot work when the wrapped descriptor mimics classmethod. The current PR fixesthis.In Python 3.8 and before, one could create a callable descriptor such that thisworks as expected (see Lib/test/test_decorators.py for examples):```pythonclass A: @myclassmethod def f1(cls): return cls@classmethod @myclassmethod def f2(cls): return cls```In Python 3.8 and before, `A.f2()` return `A`. Currently in Python 3.9, itreturns `type(A)`. This PR make `A.f2()` return `A` again.As ofGH-8405, classmethod calls `obj.__get__(type)` if `obj` has `__get__`.This allows one to chain `@classmethod` and `@property` together. Whenusing classmethod-like descriptors, it's the second argument to `__get__`--theowner or the type--that is important, but this argument is currently missing.Since it is None, the "owner" argument is assumed to be the type of the firstargument, which, in this case, is wrong (we want `A`, not `type(A)`).This PR updates classmethod to call `obj.__get__(type, type)` if `obj` has`__get__`.Co-authored-by: Erik Welch <erik.n.welch@gmail.com>(cherry picked from commitb83861f)
1 parent3026d13 commit2ce8af3

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
fromtestimportsupport
22
importunittest
3+
fromtypesimportMethodType
34

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

333+
deftest_wrapped_classmethod_inside_classmethod(self):
334+
classMyClassMethod1:
335+
def__init__(self,func):
336+
self.func=func
337+
338+
def__call__(self,cls):
339+
ifhasattr(self.func,'__get__'):
340+
returnself.func.__get__(cls,cls)()
341+
returnself.func(cls)
342+
343+
def__get__(self,instance,owner=None):
344+
ifownerisNone:
345+
owner=type(instance)
346+
returnMethodType(self,owner)
347+
348+
classMyClassMethod2:
349+
def__init__(self,func):
350+
ifisinstance(func,classmethod):
351+
func=func.__func__
352+
self.func=func
353+
354+
def__call__(self,cls):
355+
returnself.func(cls)
356+
357+
def__get__(self,instance,owner=None):
358+
ifownerisNone:
359+
owner=type(instance)
360+
returnMethodType(self,owner)
361+
362+
formyclassmethodin [MyClassMethod1,MyClassMethod2]:
363+
classA:
364+
@myclassmethod
365+
deff1(cls):
366+
returncls
367+
368+
@classmethod
369+
@myclassmethod
370+
deff2(cls):
371+
returncls
372+
373+
@myclassmethod
374+
@classmethod
375+
deff3(cls):
376+
returncls
377+
378+
@classmethod
379+
@classmethod
380+
deff4(cls):
381+
returncls
382+
383+
@myclassmethod
384+
@MyClassMethod1
385+
deff5(cls):
386+
returncls
387+
388+
@myclassmethod
389+
@MyClassMethod2
390+
deff6(cls):
391+
returncls
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+
deff(cls):
408+
returncls
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
classTestClassDecorators(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
@@ -825,7 +825,7 @@ cm_descr_get(PyObject *self, PyObject *obj, PyObject *type)
825825
type= (PyObject*)(Py_TYPE(obj));
826826
if (Py_TYPE(cm->cm_callable)->tp_descr_get!=NULL) {
827827
returnPy_TYPE(cm->cm_callable)->tp_descr_get(cm->cm_callable,type,
828-
NULL);
828+
type);
829829
}
830830
returnPyMethod_New(cm->cm_callable,type);
831831
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp