gh-103968: Deprecate creating heap types whose metaclass has custom t… · python/cpython@524a7f7 · GitHub
Skip to content

Commit 524a7f7

Browse files
encukoubarneygale
andauthored
gh-103968: Deprecate creating heap types whose metaclass has custom tp_new. (GH-103972)
(That's a mouthful of an edge case!) Co-authored-by: Barney Gale <barney.gale@gmail.com>
1 parent 423d7fa commit 524a7f7

6 files changed

Lines changed: 106 additions & 12 deletions

File tree

Doc/c-api/type.rst

Lines changed: 24 additions & 2 deletions

Doc/whatsnew/3.12.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1320,6 +1320,21 @@ Porting to Python 3.12
13201320
available on debug builds. If you happen to be using it then you'll
13211321
need to start using ``_Py_GetGlobalRefTotal()``.
13221322

1323+
* The following functions now select an appropriate metaclass for the newly
1324+
created type:
1325+
1326+
* :c:func:`PyType_FromSpec`
1327+
* :c:func:`PyType_FromSpecWithBases`
1328+
* :c:func:`PyType_FromModuleAndSpec`
1329+
1330+
Creating classes whose metaclass overrides :c:member:`~PyTypeObject.tp_new`
1331+
is deprecated, and in Python 3.14+ it will be disallowed.
1332+
Note that these functions ignore ``tp_new`` of the metaclass, possibly
1333+
allowing incomplete initialization.
1334+
1335+
Note that :c:func:`PyType_FromMetaclass` (added in Python 3.12)
1336+
already disallows creating classes whose metaclass overrides ``tp_new``.
1337+
13231338
Deprecated
13241339
----------
13251340

@@ -1396,6 +1411,11 @@ Deprecated
13961411
* ``_PyErr_ChainExceptions`` is deprecated. Use ``_PyErr_ChainExceptions1``
13971412
instead. (Contributed by Irit Katriel in :gh:`102192`.)
13981413

1414+
* Using :c:func:`PyType_FromSpec`, :c:func:`PyType_FromSpecWithBases`
1415+
or :c:func:`PyType_FromModuleAndSpec` to create a class whose metaclass
1416+
overrides :c:member:`~PyTypeObject.tp_new` is deprecated.
1417+
Call the metaclass instead.
1418+
13991419
Removed
14001420
-------
14011421

Lib/test/test_capi/test_misc.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,20 @@ def test_heaptype_with_custom_metaclass(self):
681681
with self.assertRaisesRegex(TypeError, msg):
682682
t = _testcapi.pytype_fromspec_meta(_testcapi.HeapCTypeMetaclassCustomNew)
683683

684+
def test_heaptype_with_custom_metaclass_deprecation(self):
685+
# gh-103968: a metaclass with custom tp_new is deprecated, but still
686+
# allowed for functions that existed in 3.11
687+
# (PyType_FromSpecWithBases is used here).
688+
class Base(metaclass=_testcapi.HeapCTypeMetaclassCustomNew):
689+
pass
690+
691+
with warnings_helper.check_warnings(
692+
('.*custom tp_new.*in Python 3.14.*', DeprecationWarning),
693+
):
694+
sub = _testcapi.make_type_with_base(Base)
695+
self.assertTrue(issubclass(sub, Base))
696+
self.assertIsInstance(sub, _testcapi.HeapCTypeMetaclassCustomNew)
697+
684698
def test_multiple_inheritance_ctypes_with_weakref_or_dict(self):
685699

686700
with self.assertRaises(TypeError):
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:c:func:`PyType_FromSpec` and its variants now allow creating classes whose
2+
metaclass overrides :c:member:`~PyTypeObject.tp_new`. The ``tp_new`` is
3+
ignored. This behavior is deprecated and will be disallowed in 3.14+. The
4+
new :c:func:`PyType_FromMetaclass` already disallows it.

Modules/_testcapi/heaptype.c

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ static PyObject *pytype_fromspec_meta(PyObject* self, PyObject *meta)
2222
"_testcapi.HeapCTypeViaMetaclass",
2323
sizeof(PyObject),
2424
0,
25-
Py_TPFLAGS_DEFAULT,
25+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
2626
HeapCTypeViaMetaclass_slots
2727
};
2828

@@ -385,6 +385,19 @@ make_immutable_type_with_base(PyObject *self, PyObject *base)
385385
return PyType_FromSpecWithBases(&ImmutableSubclass_spec, base);
386386
}
387387

388+
static PyObject *
389+
make_type_with_base(PyObject *self, PyObject *base)
390+
{
391+
assert(PyType_Check(base));
392+
PyType_Spec ImmutableSubclass_spec = {
393+
.name = "_testcapi.Subclass",
394+
.basicsize = (int)((PyTypeObject*)base)->tp_basicsize,
395+
.slots = empty_type_slots,
396+
.flags = Py_TPFLAGS_DEFAULT,
397+
};
398+
return PyType_FromSpecWithBases(&ImmutableSubclass_spec, base);
399+
}
400+
388401

389402
static PyMethodDef TestMethods[] = {
390403
{"pytype_fromspec_meta", pytype_fromspec_meta, METH_O},
@@ -397,6 +410,7 @@ static PyMethodDef TestMethods[] = {
397410
test_from_spec_invalid_metatype_inheritance,
398411
METH_NOARGS},
399412
{"make_immutable_type_with_base", make_immutable_type_with_base, METH_O},
413+
{"make_type_with_base", make_type_with_base, METH_O},
400414
{NULL},
401415
};
402416

Objects/typeobject.c

Lines changed: 29 additions & 9 deletions

0 commit comments

Comments
 (0)