Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

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
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,13 @@ The following functions and structs are used to create
The metaclass *metaclass* is used to construct the resulting type object.
When *metaclass* is ``NULL``, the metaclass is derived from *bases*
(or *Py_tp_base[s]* slots if *bases* is ``NULL``, see below).
Note that metaclasses that override
:c:member:`~PyTypeObject.tp_new` are not supported.

Metaclasses that override :c:member:`~PyTypeObject.tp_new` are not
supported.
(For backwards compatibility, other ``PyType_From*`` functions allow
such metaclasses. They ignore ``tp_new``, which may result in incomplete
initialization. This is deprecated and in Python 3.14+ such metaclasses will
not be supported.)
Comment on lines +262 to +265
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be nested in a .. deprecated:: 3.12 block I suppose? And below.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's mentioned in .. versionchanged:: 3.12 in the affected functions.
Their docs are just Equivalent to ``PyType_FromMetaclass(NULL, NULL, arg, NULL, NULL)`` and a bunch of change notes, so the PyType_FromMetaclass docs need this parenthetical to be complete.


The *bases* argument can be used to specify base classes; it can either
be only one class or a tuple of classes.
Expand Down Expand Up @@ -305,6 +310,11 @@ The following functions and structs are used to create
The function now finds and uses a metaclass corresponding to the provided
base classes. Previously, only :class:`type` instances were returned.

The :c:member:`~PyTypeObject.tp_new` of the metaclass is *ignored*.
which may result in incomplete initialization.
Creating classes whose metaclass overrides
:c:member:`~PyTypeObject.tp_new` is deprecated and in Python 3.14+ it
will be no longer allowed.

.. c:function:: PyObject* PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)

Expand All @@ -317,6 +327,12 @@ The following functions and structs are used to create
The function now finds and uses a metaclass corresponding to the provided
base classes. Previously, only :class:`type` instances were returned.

The :c:member:`~PyTypeObject.tp_new` of the metaclass is *ignored*.
which may result in incomplete initialization.
Creating classes whose metaclass overrides
:c:member:`~PyTypeObject.tp_new` is deprecated and in Python 3.14+ it
will be no longer allowed.

.. c:function:: PyObject* PyType_FromSpec(PyType_Spec *spec)

Equivalent to ``PyType_FromMetaclass(NULL, NULL, spec, NULL)``.
Expand All @@ -327,6 +343,12 @@ The following functions and structs are used to create
base classes provided in *Py_tp_base[s]* slots.
Previously, only :class:`type` instances were returned.

The :c:member:`~PyTypeObject.tp_new` of the metaclass is *ignored*.
which may result in incomplete initialization.
Creating classes whose metaclass overrides
:c:member:`~PyTypeObject.tp_new` is deprecated and in Python 3.14+ it
will be no longer allowed.

.. c:type:: PyType_Spec

Structure defining a type's behavior.
Expand Down
20 changes: 20 additions & 0 deletions Doc/whatsnew/3.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1288,6 +1288,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 no longer allowed.
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 @@ -1364,6 +1379,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`.
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
36 changes: 28 additions & 8 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -3634,8 +3634,9 @@ check_basicsize_includes_size_and_offsets(PyTypeObject* type)
}

PyObject *
PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
PyType_Spec *spec, PyObject *bases_in)
_PyType_FromMetaclass_impl(
PyTypeObject *metaclass, PyObject *module,
PyType_Spec *spec, PyObject *bases_in, int _allow_tp_new)
{
/* Invariant: A non-NULL value in one of these means this function holds
* a strong reference or owns allocated memory.
Expand Down Expand Up @@ -3810,9 +3811,21 @@ PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
goto finally;
}
if (metaclass->tp_new != PyType_Type.tp_new) {
PyErr_SetString(PyExc_TypeError,
"Metaclasses with custom tp_new are not supported.");
goto finally;
if (_allow_tp_new) {
if (PyErr_WarnFormat(
PyExc_DeprecationWarning, 1,
"Using PyType_Spec with metaclasses that have custom "
"tp_new is deprecated and will no longer be allowed in "
"Python 3.14.") < 0) {
goto finally;
}
}
else {
PyErr_SetString(
PyExc_TypeError,
"Metaclasses with custom tp_new are not supported.");
goto finally;
}
}

/* Calculate best base, and check that all bases are type objects */
Expand Down Expand Up @@ -3998,22 +4011,29 @@ PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
return (PyObject*)res;
}

PyObject *
PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
PyType_Spec *spec, PyObject *bases_in)
{
return _PyType_FromMetaclass_impl(metaclass, module, spec, bases_in, 0);
}

PyObject *
PyType_FromModuleAndSpec(PyObject *module, PyType_Spec *spec, PyObject *bases)
{
return PyType_FromMetaclass(NULL, module, spec, bases);
return _PyType_FromMetaclass_impl(NULL, module, spec, bases, 1);
}

PyObject *
PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
{
return PyType_FromMetaclass(NULL, NULL, spec, bases);
return _PyType_FromMetaclass_impl(NULL, NULL, spec, bases, 1);
}

PyObject *
PyType_FromSpec(PyType_Spec *spec)
{
return PyType_FromMetaclass(NULL, NULL, spec, NULL);
return _PyType_FromMetaclass_impl(NULL, NULL, spec, NULL, 1);
}

PyObject *
Expand Down