diff --git a/Lib/test/test_decorators.py b/Lib/test/test_decorators.py index a6baa3ad1dd6b3..37e4688922e412 100644 --- a/Lib/test/test_decorators.py +++ b/Lib/test/test_decorators.py @@ -318,12 +318,13 @@ def inner(cls): # This should already work. return 'spam' - @classmethod - @decorator - def outer(cls): - # Raised TypeError with a message saying that the 'Wrapper' - # object is not callable. - return 'eggs' + with self.assertWarns(FutureWarning): + @classmethod + @decorator + def outer(cls): + # Raised TypeError with a message saying that the 'Wrapper' + # object is not callable. + return 'eggs' self.assertEqual(Class.inner(), 'spam') self.assertEqual(Class.outer(), 'eggs') @@ -385,10 +386,11 @@ def f2(cls): def f3(cls): return cls - @classmethod - @classmethod - def f4(cls): - return cls + with self.assertWarns(FutureWarning): + @classmethod + @classmethod + def f4(cls): + return cls @myclassmethod @MyClassMethod1 diff --git a/Lib/test/test_doctest.py b/Lib/test/test_doctest.py index 3e7f3782d89f42..de84e398644b46 100644 --- a/Lib/test/test_doctest.py +++ b/Lib/test/test_doctest.py @@ -17,6 +17,7 @@ import shutil import types import contextlib +import warnings if not support.has_subprocess_support: @@ -103,14 +104,16 @@ def a_classmethod(cls, v): a_class_attribute = 42 - @classmethod - @property - def a_classmethod_property(cls): - """ - >>> print(SampleClass.a_classmethod_property) - 42 - """ - return cls.a_class_attribute + with warnings.catch_warnings(): + warnings.simplefilter('ignore', FutureWarning) + @classmethod + @property + def a_classmethod_property(cls): + """ + >>> print(SampleClass.a_classmethod_property) + 42 + """ + return cls.a_class_attribute class NestedClass: """ @@ -460,7 +463,7 @@ def basics(): r""" >>> tests = finder.find(sample_func) >>> print(tests) # doctest: +ELLIPSIS - [] + [] The exact name depends on how test_doctest was invoked, so allow for leading path components. diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index d91ad1c191275e..b6fceaa6de0a3b 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -187,10 +187,11 @@ def test_refleaks_in___init__(self): "Docstrings are omitted with -O2 and above") def test_class_property(self): class A: - @classmethod - @property - def __doc__(cls): - return 'A doc for %r' % cls.__name__ + with self.assertWarns(FutureWarning): + @classmethod + @property + def __doc__(cls): + return 'A doc for %r' % cls.__name__ self.assertEqual(A.__doc__, "A doc for 'A'") @unittest.skipIf(sys.flags.optimize >= 2, @@ -198,10 +199,11 @@ def __doc__(cls): def test_class_property_override(self): class A: """First""" - @classmethod - @property - def __doc__(cls): - return 'Second' + with self.assertWarns(FutureWarning): + @classmethod + @property + def __doc__(cls): + return 'Second' self.assertEqual(A.__doc__, 'Second') def test_property_set_name_incorrect_args(self): diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-05-07-16-13-15.gh-issue-89519.hRQ7Xx.rst b/Misc/NEWS.d/next/Core and Builtins/2022-05-07-16-13-15.gh-issue-89519.hRQ7Xx.rst new file mode 100644 index 00000000000000..b920fe66b4bcec --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-05-07-16-13-15.gh-issue-89519.hRQ7Xx.rst @@ -0,0 +1,2 @@ +Emit :exc:`FutureWarning` when :func:`classmethod` wraps a non-callable +descriptor (such as :class:`property`). diff --git a/Objects/funcobject.c b/Objects/funcobject.c index 32b4155c03e6ae..cd4aa038e2178c 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -896,7 +896,9 @@ cm_descr_get(PyObject *self, PyObject *obj, PyObject *type) } if (type == NULL) type = (PyObject *)(Py_TYPE(obj)); - if (Py_TYPE(cm->cm_callable)->tp_descr_get != NULL) { + if (Py_TYPE(cm->cm_callable)->tp_descr_get != NULL && + !PyCallable_Check(cm->cm_callable)) + { return Py_TYPE(cm->cm_callable)->tp_descr_get(cm->cm_callable, type, type); } @@ -913,6 +915,14 @@ cm_init(PyObject *self, PyObject *args, PyObject *kwds) return -1; if (!PyArg_UnpackTuple(args, "classmethod", 1, 1, &callable)) return -1; + if (Py_TYPE(callable)->tp_descr_get != NULL && !PyCallable_Check(callable)) { + if (PyErr_WarnFormat(PyExc_FutureWarning, 1, + "chaining classmethod and %.200s is deprecated", + Py_TYPE(callable)->tp_name)) + { + return -1; + } + } Py_INCREF(callable); Py_XSETREF(cm->cm_callable, callable);