diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index 3cac2c8b213c80..990bba3a9139c8 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -1438,34 +1438,41 @@ All of the following functions must be called after :c:func:`Py_Initialize`. .. versionadded:: 3.8 -.. c:type:: PyObject* (*_PyFrameEvalFunction)(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag) +.. c:type:: PyObject* (*PyUnstable_FrameEvalFunction)(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag) Type of a frame evaluation function. The *throwflag* parameter is used by the ``throw()`` method of generators: if non-zero, handle the current exception. + .. versionadded:: 3.6 + as ``_PyFrameEvalFunction``. + .. versionchanged:: 3.9 The function now takes a *tstate* parameter. .. versionchanged:: 3.11 The *frame* parameter changed from ``PyFrameObject*`` to ``_PyInterpreterFrame*``. -.. c:function:: _PyFrameEvalFunction _PyInterpreterState_GetEvalFrameFunc(PyInterpreterState *interp) + .. versionchanged:: next + Renamed to :c:type:`!PyUnstable_FrameEvalFunction`. The old private name + is deprecated, but will be available until the API changes. + +.. c:function:: PyUnstable_FrameEvalFunction PyUnstable_InterpreterState_GetEvalFrameFunc(PyInterpreterState *interp) Get the frame evaluation function. See the :pep:`523` "Adding a frame evaluation API to CPython". - .. versionadded:: 3.9 + .. versionadded:: next -.. c:function:: void _PyInterpreterState_SetEvalFrameFunc(PyInterpreterState *interp, _PyFrameEvalFunction eval_frame) +.. c:function:: void PyUnstable_InterpreterState_SetEvalFrameFunc(PyInterpreterState *interp, PyUnstable_FrameEvalFunction eval_frame) Set the frame evaluation function. See the :pep:`523` "Adding a frame evaluation API to CPython". - .. versionadded:: 3.9 + .. versionadded:: next .. c:function:: PyObject* PyThreadState_GetDict() diff --git a/Doc/deprecations/c-api-pending-removal-in-3.20.rst b/Doc/deprecations/c-api-pending-removal-in-3.20.rst index 18623b19a2ab8d..076ef76108b722 100644 --- a/Doc/deprecations/c-api-pending-removal-in-3.20.rst +++ b/Doc/deprecations/c-api-pending-removal-in-3.20.rst @@ -7,3 +7,10 @@ Pending removal in Python 3.20 representation. * Macros :c:macro:`!Py_MATH_PIl` and :c:macro:`!Py_MATH_El`. + +* :c:func:`!_PyInterpreterState_GetEvalFrameFunc` and + :c:func:`!_PyInterpreterState_SetEvalFrameFunc` functions are deprecated + and will be removed in Python 3.20. Use + :c:func:`PyUnstable_InterpreterState_GetEvalFrameFunc` and + :c:func:`PyUnstable_InterpreterState_SetEvalFrameFunc` instead. + (Contributed by Victor Stinner in :gh:`141518`.) diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index a095d887352127..14e998aae26f28 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -2305,7 +2305,7 @@ Porting to Python 3.11 be used for ``size``. (Contributed by Kumar Aditya in :issue:`46608`.) -* :c:func:`_PyFrameEvalFunction` now takes ``_PyInterpreterFrame*`` +* :c:func:`!_PyFrameEvalFunction` now takes ``_PyInterpreterFrame*`` as its second parameter, instead of ``PyFrameObject*``. See :pep:`523` for more details of how to use this function pointer type. diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 5a98297d3f8847..086bc3454e6ec5 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -1091,6 +1091,14 @@ New features * Add :c:func:`PyTuple_FromArray` to create a :class:`tuple` from an array. (Contributed by Victor Stinner in :gh:`111489`.) +* Add a PyUnstable API for :pep:`523`: + + * :c:type:`PyUnstable_FrameEvalFunction` type + * :c:func:`PyUnstable_InterpreterState_GetEvalFrameFunc` + * :c:func:`PyUnstable_InterpreterState_SetEvalFrameFunc` + + (Contributed by Victor Stinner in :gh:`141518`.) + * Add :c:func:`PyUnstable_Object_Dump` to dump an object to ``stderr``. It should only be used for debugging. (Contributed by Victor Stinner in :gh:`141070`.) @@ -1227,6 +1235,13 @@ Deprecated C APIs since 3.15 and will be removed in 3.20. (Contributed by Sergey B Kirpichev in :gh:`141004`.) +* :c:func:`!_PyInterpreterState_GetEvalFrameFunc` and + :c:func:`!_PyInterpreterState_SetEvalFrameFunc` functions are deprecated + and will be removed in Python 3.20. Use + :c:func:`PyUnstable_InterpreterState_GetEvalFrameFunc` and + :c:func:`PyUnstable_InterpreterState_SetEvalFrameFunc` instead. + (Contributed by Victor Stinner in :gh:`141518`.) + .. Add C API deprecations above alphabetically, not here at the end. diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 1e1e46ea4c0bcd..7e3f2e02141f60 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -302,10 +302,26 @@ PyAPI_FUNC(void) PyThreadState_DeleteCurrent(void); /* Frame evaluation API */ -typedef PyObject* (*_PyFrameEvalFunction)(PyThreadState *tstate, struct _PyInterpreterFrame *, int); +typedef PyObject* (*PyUnstable_FrameEvalFunction)(PyThreadState *tstate, struct _PyInterpreterFrame *, int); -PyAPI_FUNC(_PyFrameEvalFunction) _PyInterpreterState_GetEvalFrameFunc( +PyAPI_FUNC(PyUnstable_FrameEvalFunction) PyUnstable_InterpreterState_GetEvalFrameFunc( PyInterpreterState *interp); -PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc( +PyAPI_FUNC(void) PyUnstable_InterpreterState_SetEvalFrameFunc( PyInterpreterState *interp, - _PyFrameEvalFunction eval_frame); + PyUnstable_FrameEvalFunction eval_frame); + +// Deprecated aliases kept for backward compatibility +#define _PyFrameEvalFunction PyUnstable_FrameEvalFunction + +Py_DEPRECATED(3.15) static inline PyUnstable_FrameEvalFunction +_PyInterpreterState_GetEvalFrameFunc(PyInterpreterState *interp) +{ + return PyUnstable_InterpreterState_GetEvalFrameFunc(interp); +} + +Py_DEPRECATED(3.15) static inline void +_PyInterpreterState_SetEvalFrameFunc(PyInterpreterState *interp, + PyUnstable_FrameEvalFunction eval_frame) +{ + PyUnstable_InterpreterState_SetEvalFrameFunc(interp, eval_frame); +} diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index f861d3abd96d48..04a77966fecc89 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -87,7 +87,7 @@ struct _ceval_runtime_state { struct trampoline_api_st trampoline_api; FILE *map_file; Py_ssize_t persist_after_fork; - _PyFrameEvalFunction prev_eval_frame; + PyUnstable_FrameEvalFunction prev_eval_frame; #else int _not_used; #endif @@ -860,7 +860,7 @@ struct _is { PyObject *sysdict_copy; PyObject *builtins_copy; // Initialized to _PyEval_EvalFrameDefault(). - _PyFrameEvalFunction eval_frame; + PyUnstable_FrameEvalFunction eval_frame; PyFunction_WatchCallback func_watchers[FUNC_MAX_WATCHERS]; // One bit is set for each non-NULL entry in func_watchers diff --git a/Misc/NEWS.d/3.9.0a5.rst b/Misc/NEWS.d/3.9.0a5.rst index 9402e5077c2e77..f22a87ba356efd 100644 --- a/Misc/NEWS.d/3.9.0a5.rst +++ b/Misc/NEWS.d/3.9.0a5.rst @@ -1305,6 +1305,6 @@ and undefined PY_SSIZE_T_CLEAN whwn an exception is set. .. section: C API Add a private API to get and set the frame evaluation function: add -:c:func:`_PyInterpreterState_GetEvalFrameFunc` and -:c:func:`_PyInterpreterState_SetEvalFrameFunc` C functions. The -:c:type:`_PyFrameEvalFunction` function type now takes a *tstate* parameter. +:c:func:`!_PyInterpreterState_GetEvalFrameFunc` and +:c:func:`!_PyInterpreterState_SetEvalFrameFunc` C functions. The +:c:type:`!_PyFrameEvalFunction` function type now takes a *tstate* parameter. diff --git a/Misc/NEWS.d/next/C_API/2025-11-17-16-08-11.gh-issue-141518.Z8ig9U.rst b/Misc/NEWS.d/next/C_API/2025-11-17-16-08-11.gh-issue-141518.Z8ig9U.rst new file mode 100644 index 00000000000000..c53c3252e8648c --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2025-11-17-16-08-11.gh-issue-141518.Z8ig9U.rst @@ -0,0 +1,7 @@ +Add a PyUnstable API for :pep:`523`: + +* :c:type:`PyUnstable_FrameEvalFunction` type +* :c:func:`PyUnstable_InterpreterState_GetEvalFrameFunc` +* :c:func:`PyUnstable_InterpreterState_SetEvalFrameFunc` + +Patch by Victor Stinner. diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index c14f925b4e7632..353cbd8097623e 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2595,6 +2595,29 @@ create_managed_weakref_nogc_type(PyObject *self, PyObject *Py_UNUSED(args)) } +static PyObject * +noop_eval(PyThreadState *tstate, struct _PyInterpreterFrame *f, int exc) +{ + return NULL; +} + +static PyObject * +test_interpreter_setevalframefunc(PyObject *self, PyObject *Py_UNUSED(args)) +{ + PyInterpreterState *interp = PyInterpreterState_Get(); + PyUnstable_FrameEvalFunction eval_func; + + eval_func = PyUnstable_InterpreterState_GetEvalFrameFunc(interp); + + PyUnstable_InterpreterState_SetEvalFrameFunc(interp, noop_eval); + assert(PyUnstable_InterpreterState_GetEvalFrameFunc(interp) == noop_eval); + + PyUnstable_InterpreterState_SetEvalFrameFunc(interp, eval_func); + + Py_RETURN_NONE; +} + + static PyMethodDef TestMethods[] = { {"set_errno", set_errno, METH_VARARGS}, {"test_config", test_config, METH_NOARGS}, @@ -2691,6 +2714,8 @@ static PyMethodDef TestMethods[] = { {"toggle_reftrace_printer", toggle_reftrace_printer, METH_O}, {"create_managed_weakref_nogc_type", create_managed_weakref_nogc_type, METH_NOARGS}, + {"test_interpreter_setevalframefunc", + test_interpreter_setevalframefunc, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 89e558b0fe8933..4f6abb372585b6 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -657,7 +657,8 @@ static PyObject * set_eval_frame_default(PyObject *self, PyObject *Py_UNUSED(args)) { module_state *state = get_module_state(self); - _PyInterpreterState_SetEvalFrameFunc(_PyInterpreterState_GET(), _PyEval_EvalFrameDefault); + PyUnstable_InterpreterState_SetEvalFrameFunc(_PyInterpreterState_GET(), + _PyEval_EvalFrameDefault); Py_CLEAR(state->record_list); Py_RETURN_NONE; } @@ -689,7 +690,8 @@ set_eval_frame_record(PyObject *self, PyObject *list) return NULL; } Py_XSETREF(state->record_list, Py_NewRef(list)); - _PyInterpreterState_SetEvalFrameFunc(_PyInterpreterState_GET(), record_eval); + PyUnstable_InterpreterState_SetEvalFrameFunc(_PyInterpreterState_GET(), + record_eval); Py_RETURN_NONE; } diff --git a/Python/perf_trampoline.c b/Python/perf_trampoline.c index 987e8d2a11a659..003905b4439ff2 100644 --- a/Python/perf_trampoline.c +++ b/Python/perf_trampoline.c @@ -486,12 +486,12 @@ _PyPerfTrampoline_Init(int activate) #ifdef PY_HAVE_PERF_TRAMPOLINE PyThreadState *tstate = _PyThreadState_GET(); if (!activate) { - _PyInterpreterState_SetEvalFrameFunc(tstate->interp, prev_eval_frame); + PyUnstable_InterpreterState_SetEvalFrameFunc(tstate->interp, prev_eval_frame); perf_status = PERF_STATUS_NO_INIT; } else if (tstate->interp->eval_frame != py_trampoline_evaluator) { - prev_eval_frame = _PyInterpreterState_GetEvalFrameFunc(tstate->interp); - _PyInterpreterState_SetEvalFrameFunc(tstate->interp, py_trampoline_evaluator); + prev_eval_frame = PyUnstable_InterpreterState_GetEvalFrameFunc(tstate->interp); + PyUnstable_InterpreterState_SetEvalFrameFunc(tstate->interp, py_trampoline_evaluator); extra_code_index = _PyEval_RequestCodeExtraIndex(NULL); if (extra_code_index == -1) { return -1; @@ -517,7 +517,7 @@ _PyPerfTrampoline_Fini(void) } PyThreadState *tstate = _PyThreadState_GET(); if (tstate->interp->eval_frame == py_trampoline_evaluator) { - _PyInterpreterState_SetEvalFrameFunc(tstate->interp, NULL); + PyUnstable_InterpreterState_SetEvalFrameFunc(tstate->interp, NULL); } if (perf_status == PERF_STATUS_OK) { trampoline_api.free_state(trampoline_api.state); diff --git a/Python/pystate.c b/Python/pystate.c index c12a1418e74309..2e3c40d2be8957 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -2896,8 +2896,8 @@ PyGILState_Release(PyGILState_STATE oldstate) /* Other API */ /*************/ -_PyFrameEvalFunction -_PyInterpreterState_GetEvalFrameFunc(PyInterpreterState *interp) +PyUnstable_FrameEvalFunction +PyUnstable_InterpreterState_GetEvalFrameFunc(PyInterpreterState *interp) { if (interp->eval_frame == NULL) { return _PyEval_EvalFrameDefault; @@ -2907,8 +2907,8 @@ _PyInterpreterState_GetEvalFrameFunc(PyInterpreterState *interp) void -_PyInterpreterState_SetEvalFrameFunc(PyInterpreterState *interp, - _PyFrameEvalFunction eval_frame) +PyUnstable_InterpreterState_SetEvalFrameFunc(PyInterpreterState *interp, + PyUnstable_FrameEvalFunction eval_frame) { if (eval_frame == _PyEval_EvalFrameDefault) { eval_frame = NULL;