gh-103968: Deprecate creating heap types whose metaclass has custom tp_new. by encukou · Pull Request #103972 · python/cpython · GitHub
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
26 changes: 24 additions & 2 deletions Doc/c-api/type.rst
20 changes: 20 additions & 0 deletions Doc/whatsnew/3.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1308,6 +1308,21 @@ Porting to Python 3.12
available on debug builds. If you happen to be using it then you'll
need to start using ``_Py_GetGlobalRefTotal()``.

* The following functions now select an appropriate metaclass for the newly
created type:

* :c:func:`PyType_FromSpec`
* :c:func:`PyType_FromSpecWithBases`
* :c:func:`PyType_FromModuleAndSpec`

Creating classes whose metaclass overrides :c:member:`~PyTypeObject.tp_new`
is deprecated, and in Python 3.14+ it will be disallowed.
Note that these functions ignore ``tp_new`` of the metaclass, possibly
allowing incomplete initialization.

Note that :c:func:`PyType_FromMetaclass` (added in Python 3.12)
already disallows creating classes whose metaclass overrides ``tp_new``.

Deprecated
----------

Expand Down Expand Up @@ -1384,6 +1399,11 @@ Deprecated
* ``_PyErr_ChainExceptions`` is deprecated. Use ``_PyErr_ChainExceptions1``
instead. (Contributed by Irit Katriel in :gh:`102192`.)

* Using :c:func:`PyType_FromSpec`, :c:func:`PyType_FromSpecWithBases`
or :c:func:`PyType_FromModuleAndSpec` to create a class whose metaclass
overrides :c:member:`~PyTypeObject.tp_new` is deprecated.
Call the metaclass instead.

Removed
-------

Expand Down
14 changes: 14 additions & 0 deletions Lib/test/test_capi/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,20 @@ def test_heaptype_with_custom_metaclass(self):
with self.assertRaisesRegex(TypeError, msg):
t = _testcapi.pytype_fromspec_meta(_testcapi.HeapCTypeMetaclassCustomNew)

def test_heaptype_with_custom_metaclass_deprecation(self):
# gh-103968: a metaclass with custom tp_new is deprecated, but still
# allowed for functions that existed in 3.11
# (PyType_FromSpecWithBases is used here).
class Base(metaclass=_testcapi.HeapCTypeMetaclassCustomNew):
pass

with warnings_helper.check_warnings(
('.*custom tp_new.*in Python 3.14.*', DeprecationWarning),
):
sub = _testcapi.make_type_with_base(Base)
self.assertTrue(issubclass(sub, Base))
self.assertIsInstance(sub, _testcapi.HeapCTypeMetaclassCustomNew)

def test_multiple_inheritance_ctypes_with_weakref_or_dict(self):

with self.assertRaises(TypeError):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
:c:func:`PyType_FromSpec` and its variants now allow creating classes whose
metaclass overrides :c:member:`~PyTypeObject.tp_new`. The ``tp_new`` is
ignored. This behavior is deprecated and will be disallowed in 3.14+. The
new :c:func:`PyType_FromMetaclass` already disallows it.
16 changes: 15 additions & 1 deletion Modules/_testcapi/heaptype.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ static PyObject *pytype_fromspec_meta(PyObject* self, PyObject *meta)
"_testcapi.HeapCTypeViaMetaclass",
sizeof(PyObject),
0,
Py_TPFLAGS_DEFAULT,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
HeapCTypeViaMetaclass_slots
};

Expand Down Expand Up @@ -385,6 +385,19 @@ make_immutable_type_with_base(PyObject *self, PyObject *base)
return PyType_FromSpecWithBases(&ImmutableSubclass_spec, base);
}

static PyObject *
make_type_with_base(PyObject *self, PyObject *base)
{
assert(PyType_Check(base));
PyType_Spec ImmutableSubclass_spec = {
.name = "_testcapi.Subclass",
.basicsize = (int)((PyTypeObject*)base)->tp_basicsize,
.slots = empty_type_slots,
.flags = Py_TPFLAGS_DEFAULT,
};
return PyType_FromSpecWithBases(&ImmutableSubclass_spec, base);
}


static PyMethodDef TestMethods[] = {
{"pytype_fromspec_meta", pytype_fromspec_meta, METH_O},
Expand All @@ -397,6 +410,7 @@ static PyMethodDef TestMethods[] = {
test_from_spec_invalid_metatype_inheritance,
METH_NOARGS},
{"make_immutable_type_with_base", make_immutable_type_with_base, METH_O},
{"make_type_with_base", make_type_with_base, METH_O},
{NULL},
};

Expand Down
38 changes: 29 additions & 9 deletions Objects/typeobject.c