Skip to content

bpo-40421: Add missing getters for frame object attributes to C-API. #32114

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Mar 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
35 changes: 35 additions & 0 deletions Doc/c-api/frame.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ See also :ref:`Reflection <reflection>`.
.. versionadded:: 3.9


.. c:function:: PyObject* PyFrame_GetBuiltins(PyFrameObject *frame)

Get the *frame*'s ``f_builtins`` attribute.

Return a :term:`strong reference`. The result cannot be ``NULL``.

*frame* must not be ``NULL``.

.. versionadded:: 3.11


.. c:function:: PyCodeObject* PyFrame_GetCode(PyFrameObject *frame)

Get the *frame* code.
Expand All @@ -41,6 +52,30 @@ See also :ref:`Reflection <reflection>`.
.. versionadded:: 3.9


.. c:function:: PyObject* PyFrame_GetGenerator(PyFrameObject *frame)

Get the generator, coroutine, or async generator that owns this frame,
or ``NULL`` if this frame is not owned by a generator.
Does not raise an exception, even if the return value is ``NULL``.

Return a :term:`strong reference`, or ``NULL``.

*frame* must not be ``NULL``.

.. versionadded:: 3.11


.. c:function:: PyObject* PyFrame_GetGlobals(PyFrameObject *frame)

Get the *frame*'s ``f_globals`` attribute.

Return a :term:`strong reference`. The result cannot be ``NULL``.

*frame* must not be ``NULL``.

.. versionadded:: 3.11


.. c:function:: PyObject* PyFrame_GetLocals(PyFrameObject *frame)

Get the *frame*'s ``f_locals`` attribute (:class:`dict`).
Expand Down
11 changes: 7 additions & 4 deletions Doc/whatsnew/3.11.rst
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,9 @@ New Features
:c:func:`PyFloat_Unpack8`.
(Contributed by Victor Stinner in :issue:`46906`.)

* Add new functions to get frame object attributes:
:c:func:`PyFrame_GetBuiltins`, :c:func:`PyFrame_GetGenerator`,
:c:func:`PyFrame_GetGlobals`.

Porting to Python 3.11
----------------------
Expand Down Expand Up @@ -985,13 +988,13 @@ Porting to Python 3.11

* ``f_back``: use :c:func:`PyFrame_GetBack`.
* ``f_blockstack``: removed.
* ``f_builtins``: use ``PyObject_GetAttrString((PyObject*)frame, "f_builtins")``.
* ``f_builtins``: use :c:func:`PyFrame_GetBuiltins`.
* ``f_code``: use :c:func:`PyFrame_GetCode`.
* ``f_gen``: removed.
* ``f_globals``: use ``PyObject_GetAttrString((PyObject*)frame, "f_globals")``.
* ``f_gen``: use :c:func:`PyFrame_GetGenerator`.
* ``f_globals``: use :c:func:`PyFrame_GetGlobals`.
* ``f_iblock``: removed.
* ``f_lasti``: use ``PyObject_GetAttrString((PyObject*)frame, "f_lasti")``.
Code using ``f_lasti`` with ``PyCode_Addr2Line()`` must use
Code using ``f_lasti`` with ``PyCode_Addr2Line()`` should use
:c:func:`PyFrame_GetLineNumber` instead.
* ``f_lineno``: use :c:func:`PyFrame_GetLineNumber`
* ``f_locals``: use :c:func:`PyFrame_GetLocals`.
Expand Down
5 changes: 5 additions & 0 deletions Include/cpython/frameobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,8 @@ PyAPI_FUNC(void) PyFrame_FastToLocals(PyFrameObject *);

PyAPI_FUNC(PyFrameObject *) PyFrame_GetBack(PyFrameObject *frame);
PyAPI_FUNC(PyObject *) PyFrame_GetLocals(PyFrameObject *frame);

PyAPI_FUNC(PyObject *) PyFrame_GetGlobals(PyFrameObject *frame);
PyAPI_FUNC(PyObject *) PyFrame_GetBuiltins(PyFrameObject *frame);

PyAPI_FUNC(PyObject *) PyFrame_GetGenerator(PyFrameObject *frame);
20 changes: 20 additions & 0 deletions Lib/test/test_capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -1087,5 +1087,25 @@ class Subclass(BaseException, self.module.StateAccessType):
self.assertIs(Subclass().get_defining_module(), self.module)


class Test_FrameAPI(unittest.TestCase):

def getframe(self):
return sys._getframe()

def getgenframe(self):
yield sys._getframe()

def test_frame_getters(self):
frame = self.getframe()
self.assertEquals(frame.f_locals, _testcapi.frame_getlocals(frame))
self.assertIs(frame.f_globals, _testcapi.frame_getglobals(frame))
self.assertIs(frame.f_builtins, _testcapi.frame_getbuiltins(frame))

def test_frame_get_generator(self):
gen = self.getgenframe()
frame = next(gen)
self.assertIs(gen, _testcapi.frame_getgenerator(frame))


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add ``PyFrame_GetBuiltins``, ``PyFrame_GetGenerator`` and
``PyFrame_GetGlobals`` C-API functions to access frame object attributes
safely from C code.
44 changes: 44 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -5853,6 +5853,46 @@ test_float_unpack(PyObject *self, PyObject *args)
return PyFloat_FromDouble(d);
}

static PyObject *
frame_getlocals(PyObject *self, PyObject *frame)
{
if (!PyFrame_Check(frame)) {
PyErr_SetString(PyExc_TypeError, "argument must be a frame");
return NULL;
}
return PyFrame_GetLocals((PyFrameObject *)frame);
}

static PyObject *
frame_getglobals(PyObject *self, PyObject *frame)
{
if (!PyFrame_Check(frame)) {
PyErr_SetString(PyExc_TypeError, "argument must be a frame");
return NULL;
}
return PyFrame_GetGlobals((PyFrameObject *)frame);
}

static PyObject *
frame_getgenerator(PyObject *self, PyObject *frame)
{
if (!PyFrame_Check(frame)) {
PyErr_SetString(PyExc_TypeError, "argument must be a frame");
return NULL;
}
return PyFrame_GetGenerator((PyFrameObject *)frame);
}

static PyObject *
frame_getbuiltins(PyObject *self, PyObject *frame)
{
if (!PyFrame_Check(frame)) {
PyErr_SetString(PyExc_TypeError, "argument must be a frame");
return NULL;
}
return PyFrame_GetBuiltins((PyFrameObject *)frame);
}


static PyObject *negative_dictoffset(PyObject *, PyObject *);
static PyObject *test_buildvalue_issue38913(PyObject *, PyObject *);
Expand Down Expand Up @@ -6142,6 +6182,10 @@ static PyMethodDef TestMethods[] = {
{"test_tstate_capi", test_tstate_capi, METH_NOARGS, NULL},
{"float_pack", test_float_pack, METH_VARARGS, NULL},
{"float_unpack", test_float_unpack, METH_VARARGS, NULL},
{"frame_getlocals", frame_getlocals, METH_O, NULL},
{"frame_getglobals", frame_getglobals, METH_O, NULL},
{"frame_getgenerator", frame_getgenerator, METH_O, NULL},
{"frame_getbuiltins", frame_getbuiltins, METH_O, NULL},
{NULL, NULL} /* sentinel */
};

Expand Down
22 changes: 22 additions & 0 deletions Objects/frameobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1134,6 +1134,28 @@ PyFrame_GetLocals(PyFrameObject *frame)
return frame_getlocals(frame, NULL);
}

PyObject*
PyFrame_GetGlobals(PyFrameObject *frame)
{
return frame_getglobals(frame, NULL);
}

PyObject*
PyFrame_GetBuiltins(PyFrameObject *frame)
{
return frame_getbuiltins(frame, NULL);
}

PyObject *
PyFrame_GetGenerator(PyFrameObject *frame)
{
if (frame->f_frame->owner != FRAME_OWNED_BY_GENERATOR) {
return NULL;
}
PyGenObject *gen = _PyFrame_GetGenerator(frame->f_frame);
return Py_NewRef(gen);
}

PyObject*
_PyEval_BuiltinsFromGlobals(PyThreadState *tstate, PyObject *globals)
{
Expand Down