Return the number of free variables in *co*.
-.. c:function:: PyCodeObject* PyCode_New(int argcount, int kwonlyargcount, int nlocals, int stacksize, int flags, PyObject *code, PyObject *consts, PyObject *names, PyObject *varnames, PyObject *freevars, PyObject *cellvars, PyObject *filename, PyObject *name, int firstlineno, PyObject *linetable, PyObject *exceptiontable)
+.. c:function:: PyCodeObject* PyUnstable_Code_New(int argcount, int kwonlyargcount, int nlocals, int stacksize, int flags, PyObject *code, PyObject *consts, PyObject *names, PyObject *varnames, PyObject *freevars, PyObject *cellvars, PyObject *filename, PyObject *name, int firstlineno, PyObject *linetable, PyObject *exceptiontable)
Return a new code object. If you need a dummy code object to create a frame,
- use :c:func:`PyCode_NewEmpty` instead. Calling :c:func:`PyCode_New` directly
- will bind you to a precise Python version since the definition of the bytecode
- changes often. The many arguments of this function are inter-dependent in complex
+ use :c:func:`PyCode_NewEmpty` instead.
+
+ Since the definition of the bytecode changes often, calling
+ :c:func:`PyCode_New` directly can bind you to a precise Python version.
+
+ The many arguments of this function are inter-dependent in complex
ways, meaning that subtle changes to values are likely to result in incorrect
execution or VM crashes. Use this function only with extreme care.
.. versionchanged:: 3.11
Added ``exceptiontable`` parameter.
-.. c:function:: PyCodeObject* PyCode_NewWithPosOnlyArgs(int argcount, int posonlyargcount, int kwonlyargcount, int nlocals, int stacksize, int flags, PyObject *code, PyObject *consts, PyObject *names, PyObject *varnames, PyObject *freevars, PyObject *cellvars, PyObject *filename, PyObject *name, int firstlineno, PyObject *linetable, PyObject *exceptiontable)
+ .. index:: single: PyCode_New
+
+ .. versionchanged:: 3.12
+
+ Renamed from ``PyCode_New`` as part of :ref:`unstable-c-api`.
+ The old name is deprecated, but will remain available until the
+ signature changes again.
+
+.. c:function:: PyCodeObject* PyUnstable_Code_NewWithPosOnlyArgs(int argcount, int posonlyargcount, int kwonlyargcount, int nlocals, int stacksize, int flags, PyObject *code, PyObject *consts, PyObject *names, PyObject *varnames, PyObject *freevars, PyObject *cellvars, PyObject *filename, PyObject *name, int firstlineno, PyObject *linetable, PyObject *exceptiontable)
Similar to :c:func:`PyCode_New`, but with an extra "posonlyargcount" for positional-only arguments.
The same caveats that apply to ``PyCode_New`` also apply to this function.
- .. versionadded:: 3.8
+ .. index:: single: PyCode_NewWithPosOnlyArgs
+
+ .. versionadded:: 3.8 as ``PyCode_NewWithPosOnlyArgs``
.. versionchanged:: 3.11
Added ``exceptiontable`` parameter.
+ .. versionchanged:: 3.12
+
+ Renamed to ``PyUnstable_Code_NewWithPosOnlyArgs``.
+ The old name is deprecated, but will remain available until the
+ signature changes again.
+
.. c:function:: PyCodeObject* PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
Return a new empty code object with the specified filename,
:c:func:`PyErr_WriteUnraisable`. Otherwise it should return ``0``.
.. versionadded:: 3.12
+
+
+Extra information
+-----------------
+
+To support low-level extensions to frame evaluation, such as external
+just-in-time compilers, it is possible to attach arbitrary extra data to
+code objects.
+
+These functions are part of the unstable C API tier:
+this functionality is a CPython implementation detail, and the API
+may change without deprecation warnings.
+
+.. c:function:: Py_ssize_t PyUnstable_Eval_RequestCodeExtraIndex(freefunc free)
+
+ Return a new an opaque index value used to adding data to code objects.
+
+ You generally call this function once (per interpreter) and use the result
+ with ``PyCode_GetExtra`` and ``PyCode_SetExtra`` to manipulate
+ data on individual code objects.
+
+ If *free* is not ``NULL``: when a code object is deallocated,
+ *free* will be called on non-``NULL`` data stored under the new index.
+ Use :c:func:`Py_DecRef` when storing :c:type:`PyObject`.
+
+ .. index:: single: _PyEval_RequestCodeExtraIndex
+
+ .. versionadded:: 3.6 as ``_PyEval_RequestCodeExtraIndex``
+
+ .. versionchanged:: 3.12
+
+ Renamed to ``PyUnstable_Eval_RequestCodeExtraIndex``.
+ The old private name is deprecated, but will be available until the API
+ changes.
+
+.. c:function:: int PyUnstable_Code_GetExtra(PyObject *code, Py_ssize_t index, void **extra)
+
+ Set *extra* to the extra data stored under the given index.
+ Return 0 on success. Set an exception and return -1 on failure.
+
+ If no data was set under the index, set *extra* to ``NULL`` and return
+ 0 without setting an exception.
+
+ .. index:: single: _PyCode_GetExtra
+
+ .. versionadded:: 3.6 as ``_PyCode_GetExtra``
+
+ .. versionchanged:: 3.12
+
+ Renamed to ``PyUnstable_Code_GetExtra``.
+ The old private name is deprecated, but will be available until the API
+ changes.
+
+.. c:function:: int PyUnstable_Code_SetExtra(PyObject *code, Py_ssize_t index, void *extra)
+
+ Set the extra data stored under the given index to *extra*.
+ Return 0 on success. Set an exception and return -1 on failure.
+
+ .. index:: single: _PyCode_SetExtra
+
+ .. versionadded:: 3.6 as ``_PyCode_SetExtra``
+
+ .. versionchanged:: 3.12
+
+ Renamed to ``PyUnstable_Code_SetExtra``.
+ The old private name is deprecated, but will be available until the API
+ changes.
C API Stability
***************
-Python's C API is covered by the Backwards Compatibility Policy, :pep:`387`.
-While the C API will change with every minor release (e.g. from 3.9 to 3.10),
-most changes will be source-compatible, typically by only adding new API.
+Unless documented otherwise, Python's C API is covered by the Backwards
+Compatibility Policy, :pep:`387`.
+Most changes to it are source-compatible (typically by only adding new API).
Changing existing API or removing API is only done after a deprecation period
or to fix serious issues.
So, code compiled for Python 3.10.0 will work on 3.10.8 and vice versa,
but will need to be compiled separately for 3.9.x and 3.10.x.
+There are two tiers of C API with different stability exepectations:
+
+- *Unstable API*, may change in minor versions without a deprecation period.
+ It is marked by the ``PyUnstable`` prefix in names.
+- *Limited API*, is compatible across several minor releases.
+ When :c:macro:`Py_LIMITED_API` is defined, only this subset is exposed
+ from ``Python.h``.
+
+These are discussed in more detail below.
+
Names prefixed by an underscore, such as ``_Py_InternalState``,
are private API that can change without notice even in patch releases.
+If you need to use this API, consider reaching out to
+`CPython developers <https://discuss.python.org/c/core-dev/c-api/30>`_
+to discuss adding public API for your use case.
+
+.. _unstable-c-api:
+
+Unstable C API
+==============
+
+.. index:: single: PyUnstable
+
+Any API named with the ``PyUnstable`` prefix exposes CPython implementation
+details, and may change in every minor release (e.g. from 3.9 to 3.10) without
+any deprecation warnings.
+However, it will not change in a bugfix release (e.g. from 3.10.0 to 3.10.1).
+
+It is generally intended for specialized, low-level tools like debuggers.
+
+Projects that use this API are expected to follow
+CPython development and spend extra effort adjusting to changes.
Stable Application Binary Interface
' (Only some members are part of the stable ABI.)')
node.insert(0, emph_node)
+ # Unstable API annotation.
+ if name.startswith('PyUnstable'):
+ warn_node = nodes.admonition(
+ classes=['unstable-c-api', 'warning'])
+ message = 'This is '
+ emph_node = nodes.emphasis(message, message)
+ ref_node = addnodes.pending_xref(
+ 'Unstable API', refdomain="std",
+ reftarget='unstable-c-api',
+ reftype='ref', refexplicit="False")
+ ref_node += nodes.Text('Unstable API')
+ emph_node += ref_node
+ emph_node += nodes.Text('. It may change without warning in minor releases.')
+ warn_node += emph_node
+ node.insert(0, warn_node)
+
# Return value annotation
if objtype != 'function':
continue
New Features
------------
+
+* :pep:`697`: Introduced the :ref:`Unstable C API tier <unstable-c-api>`,
+ intended for low-level tools like debuggers and JIT compilers.
+ This API may change in each minor release of CPython without deprecation
+ warnings.
+ Its contents are marked by the ``PyUnstable_`` prefix in names.
+
+ Code object constructors:
+
+ - ``PyUnstable_Code_New()`` (renamed from ``PyCode_New``)
+ - ``PyUnstable_Code_NewWithPosOnlyArgs()`` (renamed from ``PyCode_NewWithPosOnlyArgs``)
+
+ Extra storage for code objects (:pep:`523`):
+
+ - ``PyUnstable_Eval_RequestCodeExtraIndex()`` (renamed from ``_PyEval_RequestCodeExtraIndex``)
+ - ``PyUnstable_Code_GetExtra()`` (renamed from ``_PyCode_GetExtra``)
+ - ``PyUnstable_Code_SetExtra()`` (renamed from ``_PyCode_SetExtra``)
+
+ The original names will continue to be available until the respective
+ API changes.
+
+ (Contributed by Petr Viktorin in :gh:`101101`.)
+
* Added the new limited C API function :c:func:`PyType_FromMetaclass`,
which generalizes the existing :c:func:`PyType_FromModuleAndSpec` using
an additional metaclass argument.
The Python C API
================
-The C API is divided into three sections:
+The C API is divided into these sections:
1. ``Include/``: Limited API
2. ``Include/cpython/``: CPython implementation details
-3. ``Include/internal/``: The internal API
+3. ``Include/cpython/``, names with the ``PyUnstable_`` prefix: API that can
+ change between minor releases
+4. ``Include/internal/``, and any name with ``_`` prefix: The internal API
Information on changing the C API is available `in the developer guide`_
PyAPI_FUNC(void) _PyEval_SetSwitchInterval(unsigned long microseconds);
PyAPI_FUNC(unsigned long) _PyEval_GetSwitchInterval(void);
-PyAPI_FUNC(Py_ssize_t) _PyEval_RequestCodeExtraIndex(freefunc);
+PyAPI_FUNC(Py_ssize_t) PyUnstable_Eval_RequestCodeExtraIndex(freefunc);
+// Old name -- remove when this API changes:
+_Py_DEPRECATED_EXTERNALLY(3.12) static inline Py_ssize_t
+_PyEval_RequestCodeExtraIndex(freefunc f) {
+ return PyUnstable_Eval_RequestCodeExtraIndex(f);
+}
PyAPI_FUNC(int) _PyEval_SliceIndex(PyObject *, Py_ssize_t *);
PyAPI_FUNC(int) _PyEval_SliceIndexNotNone(PyObject *, Py_ssize_t *);
#define _PyCode_CODE(CO) _Py_RVALUE((_Py_CODEUNIT *)(CO)->co_code_adaptive)
#define _PyCode_NBYTES(CO) (Py_SIZE(CO) * (Py_ssize_t)sizeof(_Py_CODEUNIT))
-/* Public interface */
-PyAPI_FUNC(PyCodeObject *) PyCode_New(
+/* Unstable public interface */
+PyAPI_FUNC(PyCodeObject *) PyUnstable_Code_New(
int, int, int, int, int, PyObject *, PyObject *,
PyObject *, PyObject *, PyObject *, PyObject *,
PyObject *, PyObject *, PyObject *, int, PyObject *,
PyObject *);
-PyAPI_FUNC(PyCodeObject *) PyCode_NewWithPosOnlyArgs(
+PyAPI_FUNC(PyCodeObject *) PyUnstable_Code_NewWithPosOnlyArgs(
int, int, int, int, int, int, PyObject *, PyObject *,
PyObject *, PyObject *, PyObject *, PyObject *,
PyObject *, PyObject *, PyObject *, int, PyObject *,
PyObject *);
/* same as struct above */
+// Old names -- remove when this API changes:
+_Py_DEPRECATED_EXTERNALLY(3.12) static inline PyCodeObject *
+PyCode_New(
+ int a, int b, int c, int d, int e, PyObject *f, PyObject *g,
+ PyObject *h, PyObject *i, PyObject *j, PyObject *k,
+ PyObject *l, PyObject *m, PyObject *n, int o, PyObject *p,
+ PyObject *q)
+{
+ return PyUnstable_Code_New(
+ a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q);
+}
+_Py_DEPRECATED_EXTERNALLY(3.12) static inline PyCodeObject *
+PyCode_NewWithPosOnlyArgs(
+ int a, int poac, int b, int c, int d, int e, PyObject *f, PyObject *g,
+ PyObject *h, PyObject *i, PyObject *j, PyObject *k,
+ PyObject *l, PyObject *m, PyObject *n, int o, PyObject *p,
+ PyObject *q)
+{
+ return PyUnstable_Code_NewWithPosOnlyArgs(
+ a, poac, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q);
+}
/* Creates a new empty code object with the specified source location. */
PyAPI_FUNC(PyCodeObject *)
PyAPI_FUNC(PyObject*) PyCode_Optimize(PyObject *code, PyObject* consts,
PyObject *names, PyObject *lnotab);
-
-PyAPI_FUNC(int) _PyCode_GetExtra(PyObject *code, Py_ssize_t index,
- void **extra);
-PyAPI_FUNC(int) _PyCode_SetExtra(PyObject *code, Py_ssize_t index,
- void *extra);
+PyAPI_FUNC(int) PyUnstable_Code_GetExtra(
+ PyObject *code, Py_ssize_t index, void **extra);
+PyAPI_FUNC(int) PyUnstable_Code_SetExtra(
+ PyObject *code, Py_ssize_t index, void *extra);
+// Old names -- remove when this API changes:
+_Py_DEPRECATED_EXTERNALLY(3.12) static inline int
+_PyCode_GetExtra(PyObject *code, Py_ssize_t index, void **extra)
+{
+ return PyUnstable_Code_GetExtra(code, index, extra);
+}
+_Py_DEPRECATED_EXTERNALLY(3.12) static inline int
+_PyCode_SetExtra(PyObject *code, Py_ssize_t index, void *extra)
+{
+ return PyUnstable_Code_SetExtra(code, index, extra);
+}
/* Equivalent to getattr(code, 'co_code') in Python.
Returns a strong reference to a bytes object. */
#define Py_DEPRECATED(VERSION_UNUSED)
#endif
+// _Py_DEPRECATED_EXTERNALLY(version)
+// Deprecated outside CPython core.
+#ifdef Py_BUILD_CORE
+#define _Py_DEPRECATED_EXTERNALLY(VERSION_UNUSED)
+#else
+#define _Py_DEPRECATED_EXTERNALLY(version) Py_DEPRECATED(version)
+#endif
+
+
#if defined(__clang__)
#define _Py_COMP_DIAG_PUSH _Pragma("clang diagnostic push")
#define _Py_COMP_DIAG_IGNORE_DEPR_DECLS \
py = ctypes.pythonapi
freefunc = ctypes.CFUNCTYPE(None,ctypes.c_voidp)
- RequestCodeExtraIndex = py._PyEval_RequestCodeExtraIndex
+ RequestCodeExtraIndex = py.PyUnstable_Eval_RequestCodeExtraIndex
RequestCodeExtraIndex.argtypes = (freefunc,)
RequestCodeExtraIndex.restype = ctypes.c_ssize_t
- SetExtra = py._PyCode_SetExtra
+ SetExtra = py.PyUnstable_Code_SetExtra
SetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t, ctypes.c_voidp)
SetExtra.restype = ctypes.c_int
- GetExtra = py._PyCode_GetExtra
+ GetExtra = py.PyUnstable_Code_GetExtra
GetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t,
ctypes.POINTER(ctypes.c_voidp))
GetExtra.restype = ctypes.c_int
--- /dev/null
+Introduced the *Unstable C API tier*, marking APi that is allowed to change
+in minor releases without a deprecation period.
+See :pep:`689` for details.
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c
-@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/unicode.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/structmember.c _testcapi/exceptions.c
+@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/unicode.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
# Some testing modules MUST be built as shared libraries.
--- /dev/null
+#include "parts.h"
+
+static Py_ssize_t
+get_code_extra_index(PyInterpreterState* interp) {
+ Py_ssize_t result = -1;
+
+ static const char *key = "_testcapi.frame_evaluation.code_index";
+
+ PyObject *interp_dict = PyInterpreterState_GetDict(interp); // borrowed
+ assert(interp_dict); // real users would handle missing dict... somehow
+
+ PyObject *index_obj = PyDict_GetItemString(interp_dict, key); // borrowed
+ Py_ssize_t index = 0;
+ if (!index_obj) {
+ if (PyErr_Occurred()) {
+ goto finally;
+ }
+ index = PyUnstable_Eval_RequestCodeExtraIndex(NULL);
+ if (index < 0 || PyErr_Occurred()) {
+ goto finally;
+ }
+ index_obj = PyLong_FromSsize_t(index); // strong ref
+ if (!index_obj) {
+ goto finally;
+ }
+ int res = PyDict_SetItemString(interp_dict, key, index_obj);
+ Py_DECREF(index_obj);
+ if (res < 0) {
+ goto finally;
+ }
+ }
+ else {
+ index = PyLong_AsSsize_t(index_obj);
+ if (index == -1 && PyErr_Occurred()) {
+ goto finally;
+ }
+ }
+
+ result = index;
+finally:
+ return result;
+}
+
+static PyObject *
+test_code_extra(PyObject* self, PyObject *Py_UNUSED(callable))
+{
+ PyObject *result = NULL;
+ PyObject *test_module = NULL;
+ PyObject *test_func = NULL;
+
+ // Get or initialize interpreter-specific code object storage index
+ PyInterpreterState *interp = PyInterpreterState_Get();
+ if (!interp) {
+ return NULL;
+ }
+ Py_ssize_t code_extra_index = get_code_extra_index(interp);
+ if (PyErr_Occurred()) {
+ goto finally;
+ }
+
+ // Get a function to test with
+ // This can be any Python function. Use `test.test_misc.testfunction`.
+ test_module = PyImport_ImportModule("test.test_capi.test_misc");
+ if (!test_module) {
+ goto finally;
+ }
+ test_func = PyObject_GetAttrString(test_module, "testfunction");
+ if (!test_func) {
+ goto finally;
+ }
+ PyObject *test_func_code = PyFunction_GetCode(test_func); // borrowed
+ if (!test_func_code) {
+ goto finally;
+ }
+
+ // Check the value is initially NULL
+ void *extra;
+ int res = PyUnstable_Code_GetExtra(test_func_code, code_extra_index, &extra);
+ if (res < 0) {
+ goto finally;
+ }
+ assert (extra == NULL);
+
+ // Set another code extra value
+ res = PyUnstable_Code_SetExtra(test_func_code, code_extra_index, (void*)(uintptr_t)77);
+ if (res < 0) {
+ goto finally;
+ }
+ // Assert it was set correctly
+ res = PyUnstable_Code_GetExtra(test_func_code, code_extra_index, &extra);
+ if (res < 0) {
+ goto finally;
+ }
+ assert ((uintptr_t)extra == 77);
+
+ result = Py_NewRef(Py_None);
+finally:
+ Py_XDECREF(test_module);
+ Py_XDECREF(test_func);
+ return result;
+}
+
+static PyMethodDef TestMethods[] = {
+ {"test_code_extra", test_code_extra, METH_NOARGS},
+ {NULL},
+};
+
+int
+_PyTestCapi_Init_Code(PyObject *m) {
+ if (PyModule_AddFunctions(m, TestMethods) < 0) {
+ return -1;
+ }
+
+ return 0;
+}
int _PyTestCapi_Init_Float(PyObject *module);
int _PyTestCapi_Init_Structmember(PyObject *module);
int _PyTestCapi_Init_Exceptions(PyObject *module);
+int _PyTestCapi_Init_Code(PyObject *module);
#ifdef LIMITED_API_AVAILABLE
int _PyTestCapi_Init_VectorcallLimited(PyObject *module);
if (_PyTestCapi_Init_Exceptions(m) < 0) {
return NULL;
}
+ if (_PyTestCapi_Init_Code(m) < 0) {
+ return NULL;
+ }
#ifndef LIMITED_API_AVAILABLE
PyModule_AddObjectRef(m, "LIMITED_API_AVAILABLE", Py_False);
******************/
PyCodeObject *
-PyCode_NewWithPosOnlyArgs(int argcount, int posonlyargcount, int kwonlyargcount,
+PyUnstable_Code_NewWithPosOnlyArgs(
+ int argcount, int posonlyargcount, int kwonlyargcount,
int nlocals, int stacksize, int flags,
PyObject *code, PyObject *consts, PyObject *names,
PyObject *varnames, PyObject *freevars, PyObject *cellvars,
}
PyCodeObject *
-PyCode_New(int argcount, int kwonlyargcount,
+PyUnstable_Code_New(int argcount, int kwonlyargcount,
int nlocals, int stacksize, int flags,
PyObject *code, PyObject *consts, PyObject *names,
PyObject *varnames, PyObject *freevars, PyObject *cellvars,
int
-_PyCode_GetExtra(PyObject *code, Py_ssize_t index, void **extra)
+PyUnstable_Code_GetExtra(PyObject *code, Py_ssize_t index, void **extra)
{
if (!PyCode_Check(code)) {
PyErr_BadInternalCall();
int
-_PyCode_SetExtra(PyObject *code, Py_ssize_t index, void *extra)
+PyUnstable_Code_SetExtra(PyObject *code, Py_ssize_t index, void *extra)
{
PyInterpreterState *interp = _PyInterpreterState_GET();
<ClCompile Include="..\Modules\_testcapi\long.c" />
<ClCompile Include="..\Modules\_testcapi\structmember.c" />
<ClCompile Include="..\Modules\_testcapi\exceptions.c" />
+ <ClCompile Include="..\Modules\_testcapi\code.c" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc" />
<ClCompile Include="..\Modules\_testcapi\exceptions.c">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\Modules\_testcapi\code.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc">
Py_ssize_t
-_PyEval_RequestCodeExtraIndex(freefunc free)
+PyUnstable_Eval_RequestCodeExtraIndex(freefunc free)
{
PyInterpreterState *interp = _PyInterpreterState_GET();
Py_ssize_t new_index;