Description
I propose to investigate an incompatible C API change: make the PyTypeObject
and PyHeapTypeObject
structures opaque, remove their members from the public C API (move them to the internal C API). We have to investigate how it's outside in 3rd party C extensions (ex: Cython, pybind11, etc.), and design a smooth migration plan.
The PyTypeObject structure is exposed as part of the public Python C API. For example, Py_TYPE(obj)->tp_name
directly gets a type name (as a UTF-8 encoded byte string, char*
).
The PyTypeObject members are NOT part of the limited C API (PEP 384).
In Python 3.9 (2020), I reworked the C API to avoid accessing directly PyTypeObject members at the ABI level: issue #84351. For example, Python 3.8 implements PyObject_IS_GC()
as a macro:
/* Test if an object has a GC head */
#define PyObject_IS_GC(o) \
(PyType_IS_GC(Py_TYPE(o)) \
&& (Py_TYPE(o)->tp_is_gc == NULL || Py_TYPE(o)->tp_is_gc(o)))
whereas Python 3.9 only provides an opaque function call:
/* Test if an object implements the garbage collector protocol */
PyAPI_FUNC(int) PyObject_IS_GC(PyObject *obj);
At the ABI level, the direct access to the PyTypeObject.tp_is_gc
member became an opaque function call.
Changing PyTypeObject API and ABI caused a lot of troubles in the past. Example:
- issue C files generated by Cython set tp_print to 0: PyTypeObject.tp_print removed #81431: Cython broken by
PyTypeObject.tp_print
removal -- related toPyTypeObject.tp_vectorcall_offset
addition - issue Remove cross-version binary compatibility #76569: ABI issue with the addition of
PyTypeObject.tp_finalize
(PEP 442). IsPy_TPFLAGS_HAVE_FINALIZE
needed for ABI compatibility? See also the python-dev thread. - issue Remove Py_TPFLAGS_HAVE_VERSION_TAG flag? #86913: remove
Py_TPFLAGS_HAVE_VERSION_TAG
flag (ABI compatibility) - issue Documentation inconsistency with the stable ABI #91271: Documentation inconsistency with the stable ABI (PyTypeObject)
- The PEP 620 uses PyTypeObject incompatible changes in its rationale.
For many years, there is a work-in-progress to convert all Python built-in types and types of stdlib extensions from static types to heap types. See for example issue #84258 and PEP 630.
The API and ABI for heap type was also enhanced over the years. Examples:
- Python 3.12 adds
PyType_FromMetaclass()
- Add the buffer protocol to the limited C API (to PyType_FromSpec()): issue Limited API support for Py_buffer #89622
- issue [C API] PyType_GetSlot cannot get tp_name #86201: get tp_name with PyType_GetSlot()
In the past, other structure members were removed:
- PyInterpreterState: Python 3.8
- PyGC_Head: Python 3.9
- PyFrameObject: Python 3.11
The work was also prepared for:
- PyObject: issue [C API] Avoid accessing PyObject and PyVarObject members directly: add Py_SET_TYPE() and Py_IS_TYPE(), disallow Py_TYPE(obj)=type #83754, Python 3.10 and Python 3.11, add Py_SET_REFCNT() and Py_SET_TYPE()
- PyTheadState: issue [C API] Prepare the C API to make PyThreadState opaque: add getter functions #84128, Python 3.11