Skip to content

Commit 5e34b49

Browse files
authored
gh-60074: add new stable API function PyType_FromMetaclass (GH-93012)
Added a new stable API function ``PyType_FromMetaclass``, which mirrors the behavior of ``PyType_FromModuleAndSpec`` except that it takes an additional metaclass argument. This is, e.g., useful for language binding tools that need to store additional information in the type object.
1 parent 20d30ba commit 5e34b49

File tree

12 files changed

+150
-14
lines changed

12 files changed

+150
-14
lines changed

Doc/c-api/type.rst

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,11 +190,16 @@ Creating Heap-Allocated Types
190190
The following functions and structs are used to create
191191
:ref:`heap types <heap-types>`.
192192
193-
.. c:function:: PyObject* PyType_FromModuleAndSpec(PyObject *module, PyType_Spec *spec, PyObject *bases)
193+
.. c:function:: PyObject* PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module, PyType_Spec *spec, PyObject *bases)
194194
195-
Creates and returns a :ref:`heap type <heap-types>` from the *spec*
195+
Create and return a :ref:`heap type <heap-types>` from the *spec*
196196
(:const:`Py_TPFLAGS_HEAPTYPE`).
197197
198+
The metaclass *metaclass* is used to construct the resulting type object.
199+
When *metaclass* is ``NULL``, the default :c:type:`PyType_Type` is used
200+
instead. Note that metaclasses that override
201+
:c:member:`~PyTypeObject.tp_new` are not supported.
202+
198203
The *bases* argument can be used to specify base classes; it can either
199204
be only one class or a tuple of classes.
200205
If *bases* is ``NULL``, the *Py_tp_bases* slot is used instead.
@@ -210,22 +215,29 @@ The following functions and structs are used to create
210215
211216
This function calls :c:func:`PyType_Ready` on the new type.
212217
218+
.. versionadded:: 3.12
219+
220+
.. c:function:: PyObject* PyType_FromModuleAndSpec(PyObject *module, PyType_Spec *spec, PyObject *bases)
221+
222+
Equivalent to ``PyType_FromMetaclass(NULL, module, spec, bases)``.
223+
213224
.. versionadded:: 3.9
214225
215226
.. versionchanged:: 3.10
216227
217228
The function now accepts a single class as the *bases* argument and
218229
``NULL`` as the ``tp_doc`` slot.
219230
231+
220232
.. c:function:: PyObject* PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
221233
222-
Equivalent to ``PyType_FromModuleAndSpec(NULL, spec, bases)``.
234+
Equivalent to ``PyType_FromMetaclass(NULL, NULL, spec, bases)``.
223235
224236
.. versionadded:: 3.3
225237
226238
.. c:function:: PyObject* PyType_FromSpec(PyType_Spec *spec)
227239
228-
Equivalent to ``PyType_FromSpecWithBases(spec, NULL)``.
240+
Equivalent to ``PyType_FromMetaclass(NULL, NULL, spec, NULL)``.
229241
230242
.. c:type:: PyType_Spec
231243

Doc/c-api/typeobj.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2071,7 +2071,7 @@ flag set.
20712071

20722072
This is done by filling a :c:type:`PyType_Spec` structure and calling
20732073
:c:func:`PyType_FromSpec`, :c:func:`PyType_FromSpecWithBases`,
2074-
or :c:func:`PyType_FromModuleAndSpec`.
2074+
:c:func:`PyType_FromModuleAndSpec`, or :c:func:`PyType_FromMetaclass`.
20752075

20762076

20772077
.. _number-structs:

Doc/data/stable_abi.dat

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Doc/whatsnew/3.12.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,11 @@ C API Changes
151151
New Features
152152
------------
153153

154+
* Added the new limited C API function :c:func:`PyType_FromMetaclass`,
155+
which generalizes the existing :c:func:`PyType_FromModuleAndSpec` using
156+
an additional metaclass argument.
157+
(Contributed by Wenzel Jakob in :gh:`93012`.)
158+
154159
Porting to Python 3.12
155160
----------------------
156161

Include/object.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,9 @@ PyAPI_FUNC(void *) PyType_GetModuleState(PyTypeObject *);
257257
PyAPI_FUNC(PyObject *) PyType_GetName(PyTypeObject *);
258258
PyAPI_FUNC(PyObject *) PyType_GetQualName(PyTypeObject *);
259259
#endif
260+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030C0000
261+
PyAPI_FUNC(PyObject *) PyType_FromMetaclass(PyTypeObject*, PyObject*, PyType_Spec*, PyObject*);
262+
#endif
260263

261264
/* Generic type check */
262265
PyAPI_FUNC(int) PyType_IsSubtype(PyTypeObject *, PyTypeObject *);

Lib/test/test_capi.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,19 @@ def test_heaptype_with_setattro(self):
605605
del obj.value
606606
self.assertEqual(obj.pvalue, 0)
607607

608+
def test_heaptype_with_custom_metaclass(self):
609+
self.assertTrue(issubclass(_testcapi.HeapCTypeMetaclass, type))
610+
self.assertTrue(issubclass(_testcapi.HeapCTypeMetaclassCustomNew, type))
611+
612+
t = _testcapi.pytype_fromspec_meta(_testcapi.HeapCTypeMetaclass)
613+
self.assertIsInstance(t, type)
614+
self.assertEqual(t.__name__, "HeapCTypeViaMetaclass")
615+
self.assertIs(type(t), _testcapi.HeapCTypeMetaclass)
616+
617+
msg = "Metaclasses with custom tp_new are not supported."
618+
with self.assertRaisesRegex(TypeError, msg):
619+
t = _testcapi.pytype_fromspec_meta(_testcapi.HeapCTypeMetaclassCustomNew)
620+
608621
def test_pynumber_tobase(self):
609622
from _testcapi import pynumber_tobase
610623
self.assertEqual(pynumber_tobase(123, 2), '0b1111011')

Lib/test/test_stable_abi_ctypes.py

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Added the new function :c:func:`PyType_FromMetaclass`, which generalizes the
2+
existing :c:func:`PyType_FromModuleAndSpec` using an additional metaclass
3+
argument. This is useful for language binding tools, where it can be used to
4+
intercept type-related operations like subclassing or static attribute access
5+
by specifying a metaclass with custom slots.
6+
7+
Importantly, :c:func:`PyType_FromMetaclass` is available in the Limited API,
8+
which provides a path towards migrating more binding tools onto the Stable ABI.

Misc/stable_abi.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2275,3 +2275,5 @@
22752275
added = '3.11'
22762276
[function.PyErr_SetHandledException]
22772277
added = '3.11'
2278+
[function.PyType_FromMetaclass]
2279+
added = '3.12'

Modules/_testcapimodule.c

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,32 @@ test_dict_inner(int count)
308308
}
309309
}
310310

311+
static PyObject *pytype_fromspec_meta(PyObject* self, PyObject *meta)
312+
{
313+
if (!PyType_Check(meta)) {
314+
PyErr_SetString(
315+
TestError,
316+
"pytype_fromspec_meta: must be invoked with a type argument!");
317+
return NULL;
318+
}
319+
320+
PyType_Slot HeapCTypeViaMetaclass_slots[] = {
321+
{0},
322+
};
323+
324+
PyType_Spec HeapCTypeViaMetaclass_spec = {
325+
"_testcapi.HeapCTypeViaMetaclass",
326+
sizeof(PyObject),
327+
0,
328+
Py_TPFLAGS_DEFAULT,
329+
HeapCTypeViaMetaclass_slots
330+
};
331+
332+
return PyType_FromMetaclass(
333+
(PyTypeObject *) meta, NULL, &HeapCTypeViaMetaclass_spec, NULL);
334+
}
335+
336+
311337
static PyObject*
312338
test_dict_iteration(PyObject* self, PyObject *Py_UNUSED(ignored))
313339
{
@@ -5886,6 +5912,7 @@ static PyMethodDef TestMethods[] = {
58865912
{"test_long_numbits", test_long_numbits, METH_NOARGS},
58875913
{"test_k_code", test_k_code, METH_NOARGS},
58885914
{"test_empty_argparse", test_empty_argparse, METH_NOARGS},
5915+
{"pytype_fromspec_meta", pytype_fromspec_meta, METH_O},
58895916
{"parse_tuple_and_keywords", parse_tuple_and_keywords, METH_VARARGS},
58905917
{"pyobject_repr_from_null", pyobject_repr_from_null, METH_NOARGS},
58915918
{"pyobject_str_from_null", pyobject_str_from_null, METH_NOARGS},
@@ -7078,6 +7105,38 @@ static PyType_Spec HeapCTypeSubclassWithFinalizer_spec = {
70787105
HeapCTypeSubclassWithFinalizer_slots
70797106
};
70807107

7108+
static PyType_Slot HeapCTypeMetaclass_slots[] = {
7109+
{0},
7110+
};
7111+
7112+
static PyType_Spec HeapCTypeMetaclass_spec = {
7113+
"_testcapi.HeapCTypeMetaclass",
7114+
sizeof(PyHeapTypeObject),
7115+
sizeof(PyMemberDef),
7116+
Py_TPFLAGS_DEFAULT,
7117+
HeapCTypeMetaclass_slots
7118+
};
7119+
7120+
static PyObject *
7121+
heap_ctype_metaclass_custom_tp_new(PyTypeObject *tp, PyObject *args, PyObject *kwargs)
7122+
{
7123+
return PyType_Type.tp_new(tp, args, kwargs);
7124+
}
7125+
7126+
static PyType_Slot HeapCTypeMetaclassCustomNew_slots[] = {
7127+
{ Py_tp_new, heap_ctype_metaclass_custom_tp_new },
7128+
{0},
7129+
};
7130+
7131+
static PyType_Spec HeapCTypeMetaclassCustomNew_spec = {
7132+
"_testcapi.HeapCTypeMetaclassCustomNew",
7133+
sizeof(PyHeapTypeObject),
7134+
sizeof(PyMemberDef),
7135+
Py_TPFLAGS_DEFAULT,
7136+
HeapCTypeMetaclassCustomNew_slots
7137+
};
7138+
7139+
70817140
typedef struct {
70827141
PyObject_HEAD
70837142
PyObject *dict;
@@ -7591,6 +7650,20 @@ PyInit__testcapi(void)
75917650
Py_DECREF(subclass_with_finalizer_bases);
75927651
PyModule_AddObject(m, "HeapCTypeSubclassWithFinalizer", HeapCTypeSubclassWithFinalizer);
75937652

7653+
PyObject *HeapCTypeMetaclass = PyType_FromMetaclass(
7654+
&PyType_Type, m, &HeapCTypeMetaclass_spec, (PyObject *) &PyType_Type);
7655+
if (HeapCTypeMetaclass == NULL) {
7656+
return NULL;
7657+
}
7658+
PyModule_AddObject(m, "HeapCTypeMetaclass", HeapCTypeMetaclass);
7659+
7660+
PyObject *HeapCTypeMetaclassCustomNew = PyType_FromMetaclass(
7661+
&PyType_Type, m, &HeapCTypeMetaclassCustomNew_spec, (PyObject *) &PyType_Type);
7662+
if (HeapCTypeMetaclassCustomNew == NULL) {
7663+
return NULL;
7664+
}
7665+
PyModule_AddObject(m, "HeapCTypeMetaclassCustomNew", HeapCTypeMetaclassCustomNew);
7666+
75947667
if (PyType_Ready(&ContainerNoGC_type) < 0) {
75957668
return NULL;
75967669
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy