Skip to content

Commit 19d2467

Browse files
committed
Issue python#23722: improve __classcell__ compatibility
Handling zero-argument super() in __init_subclass__ and __set_name__ involved moving __class__ initialisation to type.__new__. This requires cooperation from custom metaclasses to ensure that the new __classcell__ entry is passed along appropriately. The initial implementation of that change resulted in abruptly broken zero-argument super() support in metaclasses that didn't adhere to the new requirements (such as Django's metaclass for Model definitions). The updated approach adopted here instead emits a deprecation warning for those cases, and makes them work the same way they did in Python 3.5. This patch also improves the related class machinery documentation to cover these details and to include more reader-friendly cross-references and index entries.
1 parent 71c62e1 commit 19d2467

File tree

9 files changed

+1395
-1211
lines changed

9 files changed

+1395
-1211
lines changed

Doc/reference/datamodel.rst

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1700,6 +1700,10 @@ class defining the method.
17001700
Metaclasses
17011701
^^^^^^^^^^^
17021702

1703+
.. index::
1704+
single: metaclass
1705+
builtin: type
1706+
17031707
By default, classes are constructed using :func:`type`. The class body is
17041708
executed in a new namespace and the class name is bound locally to the
17051709
result of ``type(name, bases, namespace)``.
@@ -1730,6 +1734,8 @@ When a class definition is executed, the following steps occur:
17301734

17311735
Determining the appropriate metaclass
17321736
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1737+
.. index::
1738+
single: metaclass hint
17331739

17341740
The appropriate metaclass for a class definition is determined as follows:
17351741

@@ -1751,6 +1757,9 @@ that criterion, then the class definition will fail with ``TypeError``.
17511757
Preparing the class namespace
17521758
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
17531759

1760+
.. index::
1761+
single: __prepare__ (metaclass method)
1762+
17541763
Once the appropriate metaclass has been identified, then the class namespace
17551764
is prepared. If the metaclass has a ``__prepare__`` attribute, it is called
17561765
as ``namespace = metaclass.__prepare__(name, bases, **kwds)`` (where the
@@ -1768,6 +1777,9 @@ is initialised as an empty ordered mapping.
17681777
Executing the class body
17691778
^^^^^^^^^^^^^^^^^^^^^^^^
17701779

1780+
.. index::
1781+
single: class; body
1782+
17711783
The class body is executed (approximately) as
17721784
``exec(body, globals(), namespace)``. The key difference from a normal
17731785
call to :func:`exec` is that lexical scoping allows the class body (including
@@ -1777,12 +1789,19 @@ class definition occurs inside a function.
17771789
However, even when the class definition occurs inside the function, methods
17781790
defined inside the class still cannot see names defined at the class scope.
17791791
Class variables must be accessed through the first parameter of instance or
1780-
class methods, and cannot be accessed at all from static methods.
1792+
class methods, or through the implicit lexically scoped ``__class__`` reference
1793+
described in the next section.
17811794

1795+
.. _class-object-creation:
17821796

17831797
Creating the class object
17841798
^^^^^^^^^^^^^^^^^^^^^^^^^
17851799

1800+
.. index::
1801+
single: __class__ (method cell)
1802+
single: __classcell__ (class namespace entry)
1803+
1804+
17861805
Once the class namespace has been populated by executing the class body,
17871806
the class object is created by calling
17881807
``metaclass(name, bases, namespace, **kwds)`` (the additional keywords
@@ -1796,6 +1815,26 @@ created by the compiler if any methods in a class body refer to either
17961815
lexical scoping, while the class or instance that was used to make the
17971816
current call is identified based on the first argument passed to the method.
17981817

1818+
.. impl-detail::
1819+
1820+
In CPython 3.6 and later, the ``__class__`` cell is passed to the metaclass
1821+
as a ``__classcell__`` entry in the class namespace. If present, this must
1822+
be propagated up to the ``type.__new__`` call in order for the class to be
1823+
initialised correctly.
1824+
Failing to do so will result in a :exc:`DeprecationWarning` in Python 3.6,
1825+
and a :exc:`RuntimeWarning` in the future.
1826+
1827+
When using the default metaclass :class:`type`, or any metaclass that ultimately
1828+
calls ``type.__new__``, the following additional customisation steps are
1829+
invoked after creating the class object:
1830+
1831+
* first, ``type.__new__`` collects all of the descriptors in the class
1832+
namespace that define a :meth:`~object.__set_name__` method;
1833+
* second, all of these ``__set_name__`` methods are called with the class
1834+
being defined and the assigned name of that particular descriptor; and
1835+
* finally, the :meth:`~object.__init_subclass__` hook is called on the
1836+
immediate parent of the new class in its method resolution order.
1837+
17991838
After the class object is created, it is passed to the class decorators
18001839
included in the class definition (if any) and the resulting object is bound
18011840
in the local namespace as the defined class.

Doc/whatsnew/3.6.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,11 @@ whenever a new subclass is created::
351351
class Plugin2(PluginBase):
352352
pass
353353

354+
In order to allow zero-argument :func:`super` calls to work correctly from
355+
:meth:`~object.__init_subclass__` implementations, custom metaclasses must
356+
ensure that the new ``__classcell__`` namespace entry is propagated to
357+
``type.__new__`` (as described in :ref:`class-object-creation`).
358+
354359
.. seealso::
355360

356361
:pep:`487` -- Simpler customization of class creation
@@ -2235,6 +2240,11 @@ Changes in the Python API
22352240
on a ZipFile created with mode ``'r'`` will raise a :exc:`ValueError`.
22362241
Previously, a :exc:`RuntimeError` was raised in those scenarios.
22372242

2243+
* when custom metaclasses are combined with zero-argument :func:`super` or
2244+
direct references from methods to the implicit ``__class__`` closure
2245+
variable, the implicit ``__classcell__`` namespace entry must now be passed
2246+
up to ``type.__new__`` for initialisation. Failing to do so will result in
2247+
a :exc:`DeprecationWarning` in 3.6 and a :exc:`RuntimeWarning` in the future.
22382248

22392249
Changes in the C API
22402250
--------------------

Lib/importlib/_bootstrap_external.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ def _write_atomic(path, data, mode=0o666):
239239
# Python 3.6b1 3376 (simplify CALL_FUNCTIONs & BUILD_MAP_UNPACK_WITH_CALL)
240240
# Python 3.6b1 3377 (set __class__ cell from type.__new__ #23722)
241241
# Python 3.6b2 3378 (add BUILD_TUPLE_UNPACK_WITH_CALL #28257)
242+
# Python 3.6rc1 3379 (more thorough __class__ validation #23722)
242243
#
243244
# MAGIC must change whenever the bytecode emitted by the compiler may no
244245
# longer be understood by older implementations of the eval loop (usually
@@ -247,7 +248,7 @@ def _write_atomic(path, data, mode=0o666):
247248
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
248249
# in PC/launcher.c must also be updated.
249250

250-
MAGIC_NUMBER = (3378).to_bytes(2, 'little') + b'\r\n'
251+
MAGIC_NUMBER = (3379).to_bytes(2, 'little') + b'\r\n'
251252
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
252253

253254
_PYCACHE = '__pycache__'

Lib/test/test_super.py

Lines changed: 104 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
"""Unit tests for new super() implementation."""
1+
"""Unit tests for zero-argument super() & related machinery."""
22

33
import sys
44
import unittest
5+
import warnings
6+
from test.support import check_warnings
57

68

79
class A:
@@ -144,6 +146,8 @@ def f():
144146
self.assertIs(X.f(), X)
145147

146148
def test___class___new(self):
149+
# See issue #23722
150+
# Ensure zero-arg super() works as soon as type.__new__() is completed
147151
test_class = None
148152

149153
class Meta(type):
@@ -161,6 +165,7 @@ def f():
161165
self.assertIs(test_class, A)
162166

163167
def test___class___delayed(self):
168+
# See issue #23722
164169
test_namespace = None
165170

166171
class Meta(type):
@@ -169,17 +174,22 @@ def __new__(cls, name, bases, namespace):
169174
test_namespace = namespace
170175
return None
171176

172-
class A(metaclass=Meta):
173-
@staticmethod
174-
def f():
175-
return __class__
177+
# This case shouldn't trigger the __classcell__ deprecation warning
178+
with check_warnings() as w:
179+
warnings.simplefilter("always", DeprecationWarning)
180+
class A(metaclass=Meta):
181+
@staticmethod
182+
def f():
183+
return __class__
184+
self.assertEqual(w.warnings, [])
176185

177186
self.assertIs(A, None)
178187

179188
B = type("B", (), test_namespace)
180189
self.assertIs(B.f(), B)
181190

182191
def test___class___mro(self):
192+
# See issue #23722
183193
test_class = None
184194

185195
class Meta(type):
@@ -195,34 +205,105 @@ def f():
195205

196206
self.assertIs(test_class, A)
197207

198-
def test___classcell___deleted(self):
208+
def test___classcell___expected_behaviour(self):
209+
# See issue #23722
199210
class Meta(type):
200211
def __new__(cls, name, bases, namespace):
201-
del namespace['__classcell__']
212+
nonlocal namespace_snapshot
213+
namespace_snapshot = namespace.copy()
202214
return super().__new__(cls, name, bases, namespace)
203215

204-
class A(metaclass=Meta):
205-
@staticmethod
206-
def f():
207-
__class__
208-
209-
with self.assertRaises(NameError):
210-
A.f()
216+
# __classcell__ is injected into the class namespace by the compiler
217+
# when at least one method needs it, and should be omitted otherwise
218+
namespace_snapshot = None
219+
class WithoutClassRef(metaclass=Meta):
220+
pass
221+
self.assertNotIn("__classcell__", namespace_snapshot)
222+
223+
# With zero-arg super() or an explicit __class__ reference,
224+
# __classcell__ is the exact cell reference to be populated by
225+
# type.__new__
226+
namespace_snapshot = None
227+
class WithClassRef(metaclass=Meta):
228+
def f(self):
229+
return __class__
211230

212-
def test___classcell___reset(self):
231+
class_cell = namespace_snapshot["__classcell__"]
232+
method_closure = WithClassRef.f.__closure__
233+
self.assertEqual(len(method_closure), 1)
234+
self.assertIs(class_cell, method_closure[0])
235+
# Ensure the cell reference *doesn't* get turned into an attribute
236+
with self.assertRaises(AttributeError):
237+
WithClassRef.__classcell__
238+
239+
def test___classcell___missing(self):
240+
# See issue #23722
241+
# Some metaclasses may not pass the original namespace to type.__new__
242+
# We test that case here by forcibly deleting __classcell__
213243
class Meta(type):
214244
def __new__(cls, name, bases, namespace):
215-
namespace['__classcell__'] = 0
245+
namespace.pop('__classcell__', None)
216246
return super().__new__(cls, name, bases, namespace)
217247

218-
class A(metaclass=Meta):
219-
@staticmethod
220-
def f():
221-
__class__
248+
# The default case should continue to work without any warnings
249+
with check_warnings() as w:
250+
warnings.simplefilter("always", DeprecationWarning)
251+
class WithoutClassRef(metaclass=Meta):
252+
pass
253+
self.assertEqual(w.warnings, [])
254+
255+
# With zero-arg super() or an explicit __class__ reference, we expect
256+
# __build_class__ to emit a DeprecationWarning complaining that
257+
# __class__ was not set, and asking if __classcell__ was propagated
258+
# to type.__new__.
259+
# In Python 3.7, that warning will become a RuntimeError.
260+
expected_warning = (
261+
'__class__ not set.*__classcell__ propagated',
262+
DeprecationWarning
263+
)
264+
with check_warnings(expected_warning):
265+
warnings.simplefilter("always", DeprecationWarning)
266+
class WithClassRef(metaclass=Meta):
267+
def f(self):
268+
return __class__
269+
# Check __class__ still gets set despite the warning
270+
self.assertIs(WithClassRef().f(), WithClassRef)
271+
272+
# Check the warning is turned into an error as expected
273+
with warnings.catch_warnings():
274+
warnings.simplefilter("error", DeprecationWarning)
275+
with self.assertRaises(DeprecationWarning):
276+
class WithClassRef(metaclass=Meta):
277+
def f(self):
278+
return __class__
279+
280+
def test___classcell___overwrite(self):
281+
# See issue #23722
282+
# Overwriting __classcell__ with nonsense is explicitly prohibited
283+
class Meta(type):
284+
def __new__(cls, name, bases, namespace, cell):
285+
namespace['__classcell__'] = cell
286+
return super().__new__(cls, name, bases, namespace)
287+
288+
for bad_cell in (None, 0, "", object()):
289+
with self.subTest(bad_cell=bad_cell):
290+
with self.assertRaises(TypeError):
291+
class A(metaclass=Meta, cell=bad_cell):
292+
pass
222293

223-
with self.assertRaises(NameError):
224-
A.f()
225-
self.assertEqual(A.__classcell__, 0)
294+
def test___classcell___wrong_cell(self):
295+
# See issue #23722
296+
# Pointing the cell reference at the wrong class is also prohibited
297+
class Meta(type):
298+
def __new__(cls, name, bases, namespace):
299+
cls = super().__new__(cls, name, bases, namespace)
300+
B = type("B", (), namespace)
301+
return cls
302+
303+
with self.assertRaises(TypeError):
304+
class A(metaclass=Meta):
305+
def f(self):
306+
return __class__
226307

227308
def test_obscure_super_errors(self):
228309
def f():

Misc/NEWS

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ What's New in Python 3.6.0 release candidate 1
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #23722: Rather than silently producing a class that doesn't support
14+
zero-argument ``super()`` in methods, failing to pass the new
15+
``__classcell__`` namespace entry up to ``type.__new__`` now results in a
16+
``DeprecationWarning`` and a class that supports zero-argument ``super()``.
17+
1318
- Issue #28797: Modifying the class __dict__ inside the __set_name__ method of
1419
a descriptor that is used inside that class no longer prevents calling the
1520
__set_name__ method of other descriptors.
@@ -31,6 +36,13 @@ Library
3136

3237
- Issue #28843: Fix asyncio C Task to handle exceptions __traceback__.
3338

39+
Documentation
40+
-------------
41+
42+
- Issue #23722: The data model reference and the porting section in the What's
43+
New guide now cover the additional ``__classcell__`` handling needed for
44+
custom metaclasses to fully support PEP 487 and zero-argument ``super()``.
45+
3446
Tools/Demos
3547
-----------
3648

Objects/typeobject.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2687,9 +2687,16 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
26872687
else
26882688
type->tp_free = PyObject_Del;
26892689

2690-
/* store type in class' cell */
2690+
/* store type in class' cell if one is supplied */
26912691
cell = _PyDict_GetItemId(dict, &PyId___classcell__);
2692-
if (cell != NULL && PyCell_Check(cell)) {
2692+
if (cell != NULL) {
2693+
/* At least one method requires a reference to its defining class */
2694+
if (!PyCell_Check(cell)) {
2695+
PyErr_Format(PyExc_TypeError,
2696+
"__classcell__ must be a nonlocal cell, not %.200R",
2697+
Py_TYPE(cell));
2698+
goto error;
2699+
}
26932700
PyCell_Set(cell, (PyObject *) type);
26942701
_PyDict_DelItemId(dict, &PyId___classcell__);
26952702
PyErr_Clear();

0 commit comments

Comments
 (0)