mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
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 does
not work when the wrapped descriptor mimics classmethod. The current PR fixes
this.
In Python 3.8 and before, one could create a callable descriptor such that this
works as expected (see Lib/test/test_decorators.py for examples):
```python
class 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, it
returns `type(A)`. This PR make `A.f2()` return `A` again.
As of GH-8405, classmethod calls `obj.__get__(type)` if `obj` has `__get__`.
This allows one to chain `@classmethod` and `@property` together. When
using classmethod-like descriptors, it's the second argument to `__get__`--the
owner 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 first
argument, 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 commit b83861f026)
This commit is contained in:
parent
3026d1381e
commit
2ce8af3cbc
3 changed files with 89 additions and 1 deletions
|
|
@ -1,5 +1,6 @@
|
|||
from test import support
|
||||
import unittest
|
||||
from types import MethodType
|
||||
|
||||
def funcattrs(**kwds):
|
||||
def decorate(func):
|
||||
|
|
@ -329,6 +330,91 @@ def outer(cls):
|
|||
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):
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue