Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

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: 20 additions & 6 deletions Doc/c-api/frame.rst
Original file line number Diff line number Diff line change
Expand Up @@ -223,30 +223,44 @@ Internal Frames

Unless using :pep:`523`, you will not need this.

.. c:struct:: _PyInterpreterFrame
.. c:struct:: PyUnstable_InterpreterFrame

The interpreter's internal frame representation.

The structure is intentionally opaque, and there are currently no plans to
stabilize it. Debuggers and profilers can use the internal C API to access
the structure members.

.. versionadded:: 3.11
.. versionchanged:: next
Renamed to ``PyUnstable_InterpreterFrame``.

.. c:function:: PyObject* PyUnstable_InterpreterFrame_GetCode(struct _PyInterpreterFrame *frame);
.. c:function:: PyObject* PyUnstable_InterpreterFrame_GetCode(PyUnstable_InterpreterFrame *frame);

Return a :term:`strong reference` to the code object for the frame.

.. versionadded:: 3.12


.. c:function:: int PyUnstable_InterpreterFrame_GetLasti(struct _PyInterpreterFrame *frame);
.. c:function:: PyFrameObject* PyUnstable_InterpreterFrame_GetFrameObject(PyUnstable_InterpreterFrame *frame)

Get a frame object from an interpreter frame.

Return a new :term:`strong reference` on success, or set an exception and
return ``NULL`` on error.

.. versionadded:: next


.. c:function:: int PyUnstable_InterpreterFrame_GetLasti(PyUnstable_InterpreterFrame *frame);

Return the byte offset into the last executed instruction.

.. versionadded:: 3.12


.. c:function:: int PyUnstable_InterpreterFrame_GetLine(struct _PyInterpreterFrame *frame);
.. c:function:: int PyUnstable_InterpreterFrame_GetLine(PyUnstable_InterpreterFrame *frame);

Return the currently executing line number, or -1 if there is no line number.

.. versionadded:: 3.12


2 changes: 1 addition & 1 deletion Doc/c-api/init.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1438,7 +1438,7 @@ 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* (*_PyFrameEvalFunction)(PyThreadState *tstate, PyUnstable_InterpreterFrame *frame, int throwflag)
Type of a frame evaluation function.
Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1099,6 +1099,10 @@ New features
* Add :c:func:`PyTuple_FromArray` to create a :class:`tuple` from an array.
(Contributed by Victor Stinner in :gh:`111489`.)

* Add :c:func:`PyUnstable_InterpreterFrame_GetFrameObject` to get a frame
object from an interpreter frame.
(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`.)
Expand Down
14 changes: 11 additions & 3 deletions Include/cpython/pyframe.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,26 @@ PyAPI_FUNC(PyObject*) PyFrame_GetVarString(PyFrameObject *frame, const char *nam
* implementing custom frame evaluators with PEP 523. */

struct _PyInterpreterFrame;
typedef struct _PyInterpreterFrame PyUnstable_InterpreterFrame;

/* Returns the code object of the frame (strong reference).
* Does not raise an exception. */
PyAPI_FUNC(PyObject *) PyUnstable_InterpreterFrame_GetCode(struct _PyInterpreterFrame *frame);
PyAPI_FUNC(PyObject *) PyUnstable_InterpreterFrame_GetCode(
PyUnstable_InterpreterFrame *frame);

/* Returns a byte offset into the last executed instruction.
* Does not raise an exception. */
PyAPI_FUNC(int) PyUnstable_InterpreterFrame_GetLasti(struct _PyInterpreterFrame *frame);
PyAPI_FUNC(int) PyUnstable_InterpreterFrame_GetLasti(
PyUnstable_InterpreterFrame *frame);

/* Returns the currently executing line number, or -1 if there is no line number.
* Does not raise an exception. */
PyAPI_FUNC(int) PyUnstable_InterpreterFrame_GetLine(struct _PyInterpreterFrame *frame);
PyAPI_FUNC(int) PyUnstable_InterpreterFrame_GetLine(
PyUnstable_InterpreterFrame *frame);

/* Get a frame object from an interpreter frame. */
PyAPI_FUNC(PyFrameObject*) PyUnstable_InterpreterFrame_GetFrameObject(
PyUnstable_InterpreterFrame *frame);

#define PyUnstable_EXECUTABLE_KIND_SKIP 0
#define PyUnstable_EXECUTABLE_KIND_PY_FUNCTION 1
Expand Down
2 changes: 1 addition & 1 deletion Include/cpython/pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ PyAPI_FUNC(void) PyThreadState_DeleteCurrent(void);

/* Frame evaluation API */

typedef PyObject* (*_PyFrameEvalFunction)(PyThreadState *tstate, struct _PyInterpreterFrame *, int);
typedef PyObject* (*_PyFrameEvalFunction)(PyThreadState *tstate, PyUnstable_InterpreterFrame *, int);

PyAPI_FUNC(_PyFrameEvalFunction) _PyInterpreterState_GetEvalFrameFunc(
PyInterpreterState *interp);
Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_interpframe.h
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ _PyFrame_GetFrameObject(_PyInterpreterFrame *frame)
{

assert(!_PyFrame_IsIncomplete(frame));
PyFrameObject *res = frame->frame_obj;
PyFrameObject *res = frame->frame_obj;
if (res != NULL) {
return res;
}
Expand Down
30 changes: 30 additions & 0 deletions Lib/test/test_capi/test_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@


_testcapi = import_helper.import_module('_testcapi')
_testinternalcapi = import_helper.import_module('_testinternalcapi')


class FrameTest(unittest.TestCase):
Expand Down Expand Up @@ -52,5 +53,34 @@ def dummy():
self.assertIsNone(frame.f_back)


class InterpreterFrameTest(unittest.TestCase):

@staticmethod
def func():
return sys._getframe()

def test_code(self):
frame = self.func()
code = _testinternalcapi.iframe_getcode(frame)
self.assertIs(code, self.func.__code__)

def test_lasti(self):
frame = self.func()
lasti = _testinternalcapi.iframe_getlasti(frame)
self.assertGreater(lasti, 0)
self.assertLess(lasti, len(self.func.__code__.co_code))

def test_line(self):
frame = self.func()
line = _testinternalcapi.iframe_getline(frame)
firstline = self.func.__code__.co_firstlineno
self.assertEqual(line, firstline + 2)

def test_frame_object(self):
frame = sys._getframe()
obj = _testinternalcapi.iframe_getframeobject(frame)
self.assertIs(obj, frame)


if __name__ == "__main__":
unittest.main()
24 changes: 0 additions & 24 deletions Lib/test/test_capi/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2754,30 +2754,6 @@ class Subclass(BaseException, self.module.StateAccessType):
self.assertIs(Subclass().get_defining_module(), self.module)


class TestInternalFrameApi(unittest.TestCase):

@staticmethod
def func():
return sys._getframe()

def test_code(self):
frame = self.func()
code = _testinternalcapi.iframe_getcode(frame)
self.assertIs(code, self.func.__code__)

def test_lasti(self):
frame = self.func()
lasti = _testinternalcapi.iframe_getlasti(frame)
self.assertGreater(lasti, 0)
self.assertLess(lasti, len(self.func.__code__.co_code))

def test_line(self):
frame = self.func()
line = _testinternalcapi.iframe_getline(frame)
firstline = self.func.__code__.co_firstlineno
self.assertEqual(line, firstline + 2)


SUFFICIENT_TO_DEOPT_AND_SPECIALIZE = 100

class Test_Pep523API(unittest.TestCase):
Expand Down
2 changes: 1 addition & 1 deletion Misc/NEWS.d/3.14.0a1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4222,7 +4222,7 @@ docstrings are now removed from the optimized AST in optimization level 2.
.. nonce: A7uxqa
.. section: Core and Builtins
The ``f_executable`` field in the internal :c:struct:`_PyInterpreterFrame`
The ``f_executable`` field in the internal :c:struct:`!_PyInterpreterFrame`
struct now uses a tagged pointer. Profilers and debuggers that uses this
field should clear the least significant bit to recover the
:c:expr:`PyObject*` pointer.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add :c:func:`PyUnstable_InterpreterFrame_GetFrameObject` to get a frame
object from an interpreter frame. Patch by Victor Stinner.
12 changes: 12 additions & 0 deletions Modules/_testinternalcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,17 @@ iframe_getlasti(PyObject *self, PyObject *frame)
return PyLong_FromLong(PyUnstable_InterpreterFrame_GetLasti(f));
}

static PyObject *
iframe_getframeobject(PyObject *self, PyObject *frame)
{
if (!PyFrame_Check(frame)) {
PyErr_SetString(PyExc_TypeError, "argument must be a frame");
return NULL;
}
struct _PyInterpreterFrame *f = ((PyFrameObject *)frame)->f_frame;
return (PyObject*)PyUnstable_InterpreterFrame_GetFrameObject(f);
}

static PyObject *
code_returns_only_none(PyObject *self, PyObject *arg)
{
Expand Down Expand Up @@ -2529,6 +2540,7 @@ static PyMethodDef module_functions[] = {
{"iframe_getcode", iframe_getcode, METH_O, NULL},
{"iframe_getline", iframe_getline, METH_O, NULL},
{"iframe_getlasti", iframe_getlasti, METH_O, NULL},
{"iframe_getframeobject", iframe_getframeobject, METH_O, NULL},
{"code_returns_only_none", code_returns_only_none, METH_O, NULL},
{"get_co_framesize", get_co_framesize, METH_O, NULL},
{"get_co_localskinds", get_co_localskinds, METH_O, NULL},
Expand Down
7 changes: 7 additions & 0 deletions Objects/frameobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2457,3 +2457,10 @@ PyFrame_GetGenerator(PyFrameObject *frame)
assert(!_PyFrame_IsIncomplete(frame->f_frame));
return frame_generator_get((PyObject *)frame, NULL);
}


PyFrameObject*
PyUnstable_InterpreterFrame_GetFrameObject(PyUnstable_InterpreterFrame *frame)
{
return (PyFrameObject*)Py_XNewRef(_PyFrame_GetFrameObject(frame));
}
Loading