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

Commit7f9a99e

Browse files
authored
gh-89519: Remove classmethod descriptor chaining, deprecated since 3.11 (gh-110163)
1 parentee2d22f commit7f9a99e

File tree

8 files changed

+25
-193
lines changed

8 files changed

+25
-193
lines changed

‎Doc/howto/descriptor.rst

Lines changed: 10 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1141,6 +1141,16 @@ roughly equivalent to:
11411141
obj = self.__self__
11421142
return func(obj, *args, **kwargs)
11431143
1144+
def __getattribute__(self, name):
1145+
"Emulate method_getset() in Objects/classobject.c"
1146+
if name == '__doc__':
1147+
return self.__func__.__doc__
1148+
return object.__getattribute__(self, name)
1149+
1150+
def __getattr__(self, name):
1151+
"Emulate method_getattro() in Objects/classobject.c"
1152+
return getattr(self.__func__, name)
1153+
11441154
To support automatic creation of methods, functions include the
11451155
:meth:`__get__` method for binding methods during attribute access. This
11461156
means that functions are non-data descriptors that return bound methods
@@ -1420,10 +1430,6 @@ Using the non-data descriptor protocol, a pure Python version of
14201430
def __get__(self, obj, cls=None):
14211431
if cls is None:
14221432
cls = type(obj)
1423-
if hasattr(type(self.f), '__get__'):
1424-
# This code path was added in Python 3.9
1425-
# and was deprecated in Python 3.11.
1426-
return self.f.__get__(cls, cls)
14271433
return MethodType(self.f, cls)
14281434

14291435
..testcode::
@@ -1436,11 +1442,6 @@ Using the non-data descriptor protocol, a pure Python version of
14361442
"Class method that returns a tuple"
14371443
return (cls.__name__, x, y)
14381444

1439-
@ClassMethod
1440-
@property
1441-
def __doc__(cls):
1442-
return f'A doc for {cls.__name__!r}'
1443-
14441445

14451446
..doctest::
14461447
:hide:
@@ -1453,10 +1454,6 @@ Using the non-data descriptor protocol, a pure Python version of
14531454
>>>t.cm(11,22)
14541455
('T', 11, 22)
14551456

1456-
# Check the alternate path for chained descriptors
1457-
>>>T.__doc__
1458-
"A doc for 'T'"
1459-
14601457
# Verify that T uses our emulation
14611458
>>>type(vars(T)['cm']).__name__
14621459
'ClassMethod'
@@ -1481,24 +1478,6 @@ Using the non-data descriptor protocol, a pure Python version of
14811478
('T', 11, 22)
14821479

14831480

1484-
The code path for ``hasattr(type(self.f), '__get__')`` was added in
1485-
Python 3.9 and makes it possible for:func:`classmethod` to support
1486-
chained decorators. For example, a classmethod and property could be
1487-
chained together. In Python 3.11, this functionality was deprecated.
1488-
1489-
..testcode::
1490-
1491-
class G:
1492-
@classmethod
1493-
@property
1494-
def __doc__(cls):
1495-
return f'A doc for {cls.__name__!r}'
1496-
1497-
..doctest::
1498-
1499-
>>>G.__doc__
1500-
"A doc for 'G'"
1501-
15021481
The:func:`functools.update_wrapper` call in ``ClassMethod`` adds a
15031482
``__wrapped__`` attribute that refers to the underlying function. Also
15041483
it carries forward the attributes necessary to make the wrapper look

‎Doc/library/functions.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ are always available. They are listed here in alphabetical order.
285285
``__name__``, ``__qualname__``, ``__doc__`` and ``__annotations__``) and
286286
have a new ``__wrapped__`` attribute.
287287

288-
..versionchanged::3.11
288+
..deprecated-removed::3.11 3.13
289289
Class methods can no longer wrap other:term:`descriptors <descriptor>` such as
290290
:func:`property`.
291291

‎Doc/whatsnew/3.13.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,6 +1228,14 @@ Deprecated
12281228
Removed
12291229
-------
12301230

1231+
* Removed chained:class:`classmethod` descriptors (introduced in
1232+
:issue:`19072`). This can no longer be used to wrap other descriptors
1233+
such as:class:`property`. The core design of this feature was flawed
1234+
and caused a number of downstream problems. To "pass-through" a
1235+
:class:`classmethod`, consider using the:attr:`!__wrapped__`
1236+
attribute that was added in Python 3.10. (Contributed by Raymond
1237+
Hettinger in:gh:`89519`.)
1238+
12311239
* Remove many APIs (functions, macros, variables) with names prefixed by
12321240
``_Py`` or ``_PY`` (considered as private API). If your project is affected
12331241
by one of these removals and you consider that the removed API should remain

‎Lib/test/test_decorators.py

Lines changed: 0 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -291,44 +291,6 @@ def bar(): return 42
291291
self.assertEqual(bar(),42)
292292
self.assertEqual(actions,expected_actions)
293293

294-
deftest_wrapped_descriptor_inside_classmethod(self):
295-
classBoundWrapper:
296-
def__init__(self,wrapped):
297-
self.__wrapped__=wrapped
298-
299-
def__call__(self,*args,**kwargs):
300-
returnself.__wrapped__(*args,**kwargs)
301-
302-
classWrapper:
303-
def__init__(self,wrapped):
304-
self.__wrapped__=wrapped
305-
306-
def__get__(self,instance,owner):
307-
bound_function=self.__wrapped__.__get__(instance,owner)
308-
returnBoundWrapper(bound_function)
309-
310-
defdecorator(wrapped):
311-
returnWrapper(wrapped)
312-
313-
classClass:
314-
@decorator
315-
@classmethod
316-
definner(cls):
317-
# This should already work.
318-
return'spam'
319-
320-
@classmethod
321-
@decorator
322-
defouter(cls):
323-
# Raised TypeError with a message saying that the 'Wrapper'
324-
# object is not callable.
325-
return'eggs'
326-
327-
self.assertEqual(Class.inner(),'spam')
328-
self.assertEqual(Class.outer(),'eggs')
329-
self.assertEqual(Class().inner(),'spam')
330-
self.assertEqual(Class().outer(),'eggs')
331-
332294
deftest_bound_function_inside_classmethod(self):
333295
classA:
334296
deffoo(self,cls):
@@ -339,91 +301,6 @@ class B:
339301

340302
self.assertEqual(B.bar(),'spam')
341303

342-
deftest_wrapped_classmethod_inside_classmethod(self):
343-
classMyClassMethod1:
344-
def__init__(self,func):
345-
self.func=func
346-
347-
def__call__(self,cls):
348-
ifhasattr(self.func,'__get__'):
349-
returnself.func.__get__(cls,cls)()
350-
returnself.func(cls)
351-
352-
def__get__(self,instance,owner=None):
353-
ifownerisNone:
354-
owner=type(instance)
355-
returnMethodType(self,owner)
356-
357-
classMyClassMethod2:
358-
def__init__(self,func):
359-
ifisinstance(func,classmethod):
360-
func=func.__func__
361-
self.func=func
362-
363-
def__call__(self,cls):
364-
returnself.func(cls)
365-
366-
def__get__(self,instance,owner=None):
367-
ifownerisNone:
368-
owner=type(instance)
369-
returnMethodType(self,owner)
370-
371-
formyclassmethodin [MyClassMethod1,MyClassMethod2]:
372-
classA:
373-
@myclassmethod
374-
deff1(cls):
375-
returncls
376-
377-
@classmethod
378-
@myclassmethod
379-
deff2(cls):
380-
returncls
381-
382-
@myclassmethod
383-
@classmethod
384-
deff3(cls):
385-
returncls
386-
387-
@classmethod
388-
@classmethod
389-
deff4(cls):
390-
returncls
391-
392-
@myclassmethod
393-
@MyClassMethod1
394-
deff5(cls):
395-
returncls
396-
397-
@myclassmethod
398-
@MyClassMethod2
399-
deff6(cls):
400-
returncls
401-
402-
self.assertIs(A.f1(),A)
403-
self.assertIs(A.f2(),A)
404-
self.assertIs(A.f3(),A)
405-
self.assertIs(A.f4(),A)
406-
self.assertIs(A.f5(),A)
407-
self.assertIs(A.f6(),A)
408-
a=A()
409-
self.assertIs(a.f1(),A)
410-
self.assertIs(a.f2(),A)
411-
self.assertIs(a.f3(),A)
412-
self.assertIs(a.f4(),A)
413-
self.assertIs(a.f5(),A)
414-
self.assertIs(a.f6(),A)
415-
416-
deff(cls):
417-
returncls
418-
419-
self.assertIs(myclassmethod(f).__get__(a)(),A)
420-
self.assertIs(myclassmethod(f).__get__(a,A)(),A)
421-
self.assertIs(myclassmethod(f).__get__(A,A)(),A)
422-
self.assertIs(myclassmethod(f).__get__(A)(),type(A))
423-
self.assertIs(classmethod(f).__get__(a)(),A)
424-
self.assertIs(classmethod(f).__get__(a,A)(),A)
425-
self.assertIs(classmethod(f).__get__(A,A)(),A)
426-
self.assertIs(classmethod(f).__get__(A)(),type(A))
427304

428305
classTestClassDecorators(unittest.TestCase):
429306

‎Lib/test/test_doctest.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -102,15 +102,6 @@ def a_classmethod(cls, v):
102102

103103
a_class_attribute=42
104104

105-
@classmethod
106-
@property
107-
defa_classmethod_property(cls):
108-
"""
109-
>>> print(SampleClass.a_classmethod_property)
110-
42
111-
"""
112-
returncls.a_class_attribute
113-
114105
@functools.cached_property
115106
defa_cached_property(self):
116107
"""
@@ -525,7 +516,6 @@ def basics(): r"""
525516
1 SampleClass.__init__
526517
1 SampleClass.a_cached_property
527518
2 SampleClass.a_classmethod
528-
1 SampleClass.a_classmethod_property
529519
1 SampleClass.a_property
530520
1 SampleClass.a_staticmethod
531521
1 SampleClass.double
@@ -582,7 +572,6 @@ def basics(): r"""
582572
1 some_module.SampleClass.__init__
583573
1 some_module.SampleClass.a_cached_property
584574
2 some_module.SampleClass.a_classmethod
585-
1 some_module.SampleClass.a_classmethod_property
586575
1 some_module.SampleClass.a_property
587576
1 some_module.SampleClass.a_staticmethod
588577
1 some_module.SampleClass.double
@@ -625,7 +614,6 @@ def basics(): r"""
625614
1 SampleClass.__init__
626615
1 SampleClass.a_cached_property
627616
2 SampleClass.a_classmethod
628-
1 SampleClass.a_classmethod_property
629617
1 SampleClass.a_property
630618
1 SampleClass.a_staticmethod
631619
1 SampleClass.double
@@ -647,7 +635,6 @@ def basics(): r"""
647635
1 SampleClass.__init__
648636
1 SampleClass.a_cached_property
649637
2 SampleClass.a_classmethod
650-
1 SampleClass.a_classmethod_property
651638
1 SampleClass.a_property
652639
1 SampleClass.a_staticmethod
653640
1 SampleClass.double

‎Lib/test/test_property.py

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -183,27 +183,6 @@ def test_refleaks_in___init__(self):
183183
fake_prop.__init__('fget','fset','fdel','doc')
184184
self.assertAlmostEqual(gettotalrefcount()-refs_before,0,delta=10)
185185

186-
@unittest.skipIf(sys.flags.optimize>=2,
187-
"Docstrings are omitted with -O2 and above")
188-
deftest_class_property(self):
189-
classA:
190-
@classmethod
191-
@property
192-
def__doc__(cls):
193-
return'A doc for %r'%cls.__name__
194-
self.assertEqual(A.__doc__,"A doc for 'A'")
195-
196-
@unittest.skipIf(sys.flags.optimize>=2,
197-
"Docstrings are omitted with -O2 and above")
198-
deftest_class_property_override(self):
199-
classA:
200-
"""First"""
201-
@classmethod
202-
@property
203-
def__doc__(cls):
204-
return'Second'
205-
self.assertEqual(A.__doc__,'Second')
206-
207186
deftest_property_set_name_incorrect_args(self):
208187
p=property()
209188

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Removed chained:class:`classmethod` descriptors (introduced in
2+
:issue:`19072`). This can no longer be used to wrap other descriptors such
3+
as:class:`property`. The core design of this feature was flawed and caused
4+
a number of downstream problems. To "pass-through" a:class:`classmethod`,
5+
consider using the:attr:`!__wrapped__` attribute that was added in Python
6+
3.10.

‎Objects/funcobject.c

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,10 +1110,6 @@ cm_descr_get(PyObject *self, PyObject *obj, PyObject *type)
11101110
}
11111111
if (type==NULL)
11121112
type= (PyObject*)(Py_TYPE(obj));
1113-
if (Py_TYPE(cm->cm_callable)->tp_descr_get!=NULL) {
1114-
returnPy_TYPE(cm->cm_callable)->tp_descr_get(cm->cm_callable,type,
1115-
type);
1116-
}
11171113
returnPyMethod_New(cm->cm_callable,type);
11181114
}
11191115

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp