Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Doc/reference/datamodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
be propagated up to the ``type.__new__`` call in order for the class to be
initialised correctly.
Failing to do so will result in a :exc:`DeprecationWarning` in Python 3.6,
and a :exc:`RuntimeError` in Python 3.8.
Failing to do so will result in a :exc:`RuntimeError` in Python 3.8.

When using the default metaclass :class:`type`, or any metaclass that ultimately
calls ``type.__new__``, the following additional customisation steps are
Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.8.rst
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ Changes in the Python API
a database if it does not exist.
(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
------------------------
Expand Down
44 changes: 10 additions & 34 deletions Lib/test/test_super.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
"""Unit tests for zero-argument super() & related machinery."""

import unittest
import warnings
from test.support import check_warnings


class A:
Expand Down Expand Up @@ -173,14 +171,10 @@ def __new__(cls, name, bases, namespace):
test_namespace = namespace
return None

# This case shouldn't trigger the __classcell__ deprecation warning
with check_warnings() as w:
warnings.simplefilter("always", DeprecationWarning)
class A(metaclass=Meta):
@staticmethod
def f():
return __class__
self.assertEqual(w.warnings, [])
class A(metaclass=Meta):
@staticmethod
def f():
return __class__

self.assertIs(A, None)

Expand Down Expand Up @@ -244,37 +238,19 @@ def __new__(cls, name, bases, namespace):
namespace.pop('__classcell__', None)
return super().__new__(cls, name, bases, namespace)

# The default case should continue to work without any warnings
with check_warnings() as w:
warnings.simplefilter("always", DeprecationWarning)
class WithoutClassRef(metaclass=Meta):
pass
self.assertEqual(w.warnings, [])
# The default case should continue to work without any errors
class WithoutClassRef(metaclass=Meta):
pass

# 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
# to type.__new__.
# In Python 3.7, that warning will become a RuntimeError.
expected_warning = (
'__class__ not set.*__classcell__ propagated',
DeprecationWarning
)
with check_warnings(expected_warning):
warnings.simplefilter("always", DeprecationWarning)
expected_error = '__class__ not set.*__classcell__ propagated'
with self.assertRaisesRegex(RuntimeError, expected_error):
class WithClassRef(metaclass=Meta):
def f(self):
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):
# See issue #23722
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
19 changes: 4 additions & 15 deletions Python/bltinmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
PyObject *cell_cls = PyCell_GET(cell);
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) {
const char *msg =
"__class__ not set defining %.200R as %.200R. "
"Was __classcell__ propagated to type.__new__?";
cell_error = PyErr_WarnFormat(
PyExc_DeprecationWarning, 1, msg, name, cls);
PyErr_Format(PyExc_RuntimeError, msg, name, cls);
} else {
const char *msg =
"__class__ set to %.200R defining %.200R as %.200R";
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;
}
}
}
Expand Down