bpo-23722: Raise a RuntimeError for absent __classcell__. (GH-6931)
A DeprecationWarning was emitted in Python 3.6-3.7.
This commit is contained in:
parent
8ae8e6af37
commit
f5e7b1999f
|
@ -1967,8 +1967,7 @@ current call is identified based on the first argument passed to the method.
|
||||||
as a ``__classcell__`` entry in the class namespace. If present, this must
|
as a ``__classcell__`` entry in the class namespace. If present, this must
|
||||||
be propagated up to the ``type.__new__`` call in order for the class to be
|
be propagated up to the ``type.__new__`` call in order for the class to be
|
||||||
initialised correctly.
|
initialised correctly.
|
||||||
Failing to do so will result in a :exc:`DeprecationWarning` in Python 3.6,
|
Failing to do so will result in a :exc:`RuntimeError` in Python 3.8.
|
||||||
and a :exc:`RuntimeError` in Python 3.8.
|
|
||||||
|
|
||||||
When using the default metaclass :class:`type`, or any metaclass that ultimately
|
When using the default metaclass :class:`type`, or any metaclass that ultimately
|
||||||
calls ``type.__new__``, the following additional customisation steps are
|
calls ``type.__new__``, the following additional customisation steps are
|
||||||
|
|
|
@ -146,6 +146,11 @@ Changes in the Python API
|
||||||
a database if it does not exist.
|
a database if it does not exist.
|
||||||
(Contributed by Serhiy Storchaka in :issue:`32749`.)
|
(Contributed by Serhiy Storchaka in :issue:`32749`.)
|
||||||
|
|
||||||
|
* A :exc:`RuntimeError` is now raised when the custom metaclass doesn't
|
||||||
|
provide the ``__classcell__`` entry in the namespace passed to
|
||||||
|
``type.__new__``. A :exc:`DeprecationWarning` was emitted in Python
|
||||||
|
3.6--3.7. (Contributed by Serhiy Storchaka in :issue:`23722`.)
|
||||||
|
|
||||||
|
|
||||||
CPython bytecode changes
|
CPython bytecode changes
|
||||||
------------------------
|
------------------------
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
"""Unit tests for zero-argument super() & related machinery."""
|
"""Unit tests for zero-argument super() & related machinery."""
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import warnings
|
|
||||||
from test.support import check_warnings
|
|
||||||
|
|
||||||
|
|
||||||
class A:
|
class A:
|
||||||
|
@ -173,14 +171,10 @@ class TestSuper(unittest.TestCase):
|
||||||
test_namespace = namespace
|
test_namespace = namespace
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# This case shouldn't trigger the __classcell__ deprecation warning
|
class A(metaclass=Meta):
|
||||||
with check_warnings() as w:
|
@staticmethod
|
||||||
warnings.simplefilter("always", DeprecationWarning)
|
def f():
|
||||||
class A(metaclass=Meta):
|
return __class__
|
||||||
@staticmethod
|
|
||||||
def f():
|
|
||||||
return __class__
|
|
||||||
self.assertEqual(w.warnings, [])
|
|
||||||
|
|
||||||
self.assertIs(A, None)
|
self.assertIs(A, None)
|
||||||
|
|
||||||
|
@ -244,37 +238,19 @@ class TestSuper(unittest.TestCase):
|
||||||
namespace.pop('__classcell__', None)
|
namespace.pop('__classcell__', None)
|
||||||
return super().__new__(cls, name, bases, namespace)
|
return super().__new__(cls, name, bases, namespace)
|
||||||
|
|
||||||
# The default case should continue to work without any warnings
|
# The default case should continue to work without any errors
|
||||||
with check_warnings() as w:
|
class WithoutClassRef(metaclass=Meta):
|
||||||
warnings.simplefilter("always", DeprecationWarning)
|
pass
|
||||||
class WithoutClassRef(metaclass=Meta):
|
|
||||||
pass
|
|
||||||
self.assertEqual(w.warnings, [])
|
|
||||||
|
|
||||||
# With zero-arg super() or an explicit __class__ reference, we expect
|
# With zero-arg super() or an explicit __class__ reference, we expect
|
||||||
# __build_class__ to emit a DeprecationWarning complaining that
|
# __build_class__ to raise a RuntimeError complaining that
|
||||||
# __class__ was not set, and asking if __classcell__ was propagated
|
# __class__ was not set, and asking if __classcell__ was propagated
|
||||||
# to type.__new__.
|
# to type.__new__.
|
||||||
# In Python 3.7, that warning will become a RuntimeError.
|
expected_error = '__class__ not set.*__classcell__ propagated'
|
||||||
expected_warning = (
|
with self.assertRaisesRegex(RuntimeError, expected_error):
|
||||||
'__class__ not set.*__classcell__ propagated',
|
|
||||||
DeprecationWarning
|
|
||||||
)
|
|
||||||
with check_warnings(expected_warning):
|
|
||||||
warnings.simplefilter("always", DeprecationWarning)
|
|
||||||
class WithClassRef(metaclass=Meta):
|
class WithClassRef(metaclass=Meta):
|
||||||
def f(self):
|
def f(self):
|
||||||
return __class__
|
return __class__
|
||||||
# Check __class__ still gets set despite the warning
|
|
||||||
self.assertIs(WithClassRef().f(), WithClassRef)
|
|
||||||
|
|
||||||
# Check the warning is turned into an error as expected
|
|
||||||
with warnings.catch_warnings():
|
|
||||||
warnings.simplefilter("error", DeprecationWarning)
|
|
||||||
with self.assertRaises(DeprecationWarning):
|
|
||||||
class WithClassRef(metaclass=Meta):
|
|
||||||
def f(self):
|
|
||||||
return __class__
|
|
||||||
|
|
||||||
def test___classcell___overwrite(self):
|
def test___classcell___overwrite(self):
|
||||||
# See issue #23722
|
# See issue #23722
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
A :exc:`RuntimeError` is now raised when the custom metaclass doesn't
|
||||||
|
provide the ``__classcell__`` entry in the namespace passed to
|
||||||
|
``type.__new__``. A :exc:`DeprecationWarning` was emitted in Python
|
||||||
|
3.6--3.7.
|
|
@ -254,30 +254,19 @@ builtin___build_class__(PyObject *self, PyObject *const *args, Py_ssize_t nargs,
|
||||||
if (cls != NULL && PyType_Check(cls) && PyCell_Check(cell)) {
|
if (cls != NULL && PyType_Check(cls) && PyCell_Check(cell)) {
|
||||||
PyObject *cell_cls = PyCell_GET(cell);
|
PyObject *cell_cls = PyCell_GET(cell);
|
||||||
if (cell_cls != cls) {
|
if (cell_cls != cls) {
|
||||||
/* TODO: In 3.7, DeprecationWarning will become RuntimeError.
|
|
||||||
* At that point, cell_error won't be needed.
|
|
||||||
*/
|
|
||||||
int cell_error;
|
|
||||||
if (cell_cls == NULL) {
|
if (cell_cls == NULL) {
|
||||||
const char *msg =
|
const char *msg =
|
||||||
"__class__ not set defining %.200R as %.200R. "
|
"__class__ not set defining %.200R as %.200R. "
|
||||||
"Was __classcell__ propagated to type.__new__?";
|
"Was __classcell__ propagated to type.__new__?";
|
||||||
cell_error = PyErr_WarnFormat(
|
PyErr_Format(PyExc_RuntimeError, msg, name, cls);
|
||||||
PyExc_DeprecationWarning, 1, msg, name, cls);
|
|
||||||
} else {
|
} else {
|
||||||
const char *msg =
|
const char *msg =
|
||||||
"__class__ set to %.200R defining %.200R as %.200R";
|
"__class__ set to %.200R defining %.200R as %.200R";
|
||||||
PyErr_Format(PyExc_TypeError, msg, cell_cls, name, cls);
|
PyErr_Format(PyExc_TypeError, msg, cell_cls, name, cls);
|
||||||
cell_error = 1;
|
|
||||||
}
|
|
||||||
if (cell_error) {
|
|
||||||
Py_DECREF(cls);
|
|
||||||
cls = NULL;
|
|
||||||
goto error;
|
|
||||||
} else {
|
|
||||||
/* Fill in the cell, since type.__new__ didn't do it */
|
|
||||||
PyCell_Set(cell, cls);
|
|
||||||
}
|
}
|
||||||
|
Py_DECREF(cls);
|
||||||
|
cls = NULL;
|
||||||
|
goto error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue