Skip to content

Commit 74b95d8

Browse files
authored
bpo-40421: Add missing getters for frame object attributes to C-API. (GH-32114)
1 parent 44e9150 commit 74b95d8

File tree

7 files changed

+136
-4
lines changed

7 files changed

+136
-4
lines changed

Doc/c-api/frame.rst

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ See also :ref:`Reflection <reflection>`.
3030
.. versionadded:: 3.9
3131
3232
33+
.. c:function:: PyObject* PyFrame_GetBuiltins(PyFrameObject *frame)
34+
35+
Get the *frame*'s ``f_builtins`` attribute.
36+
37+
Return a :term:`strong reference`. The result cannot be ``NULL``.
38+
39+
*frame* must not be ``NULL``.
40+
41+
.. versionadded:: 3.11
42+
43+
3344
.. c:function:: PyCodeObject* PyFrame_GetCode(PyFrameObject *frame)
3445
3546
Get the *frame* code.
@@ -41,6 +52,30 @@ See also :ref:`Reflection <reflection>`.
4152
.. versionadded:: 3.9
4253
4354
55+
.. c:function:: PyObject* PyFrame_GetGenerator(PyFrameObject *frame)
56+
57+
Get the generator, coroutine, or async generator that owns this frame,
58+
or ``NULL`` if this frame is not owned by a generator.
59+
Does not raise an exception, even if the return value is ``NULL``.
60+
61+
Return a :term:`strong reference`, or ``NULL``.
62+
63+
*frame* must not be ``NULL``.
64+
65+
.. versionadded:: 3.11
66+
67+
68+
.. c:function:: PyObject* PyFrame_GetGlobals(PyFrameObject *frame)
69+
70+
Get the *frame*'s ``f_globals`` attribute.
71+
72+
Return a :term:`strong reference`. The result cannot be ``NULL``.
73+
74+
*frame* must not be ``NULL``.
75+
76+
.. versionadded:: 3.11
77+
78+
4479
.. c:function:: PyObject* PyFrame_GetLocals(PyFrameObject *frame)
4580
4681
Get the *frame*'s ``f_locals`` attribute (:class:`dict`).

Doc/whatsnew/3.11.rst

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,9 @@ New Features
868868
:c:func:`PyFloat_Unpack8`.
869869
(Contributed by Victor Stinner in :issue:`46906`.)
870870

871+
* Add new functions to get frame object attributes:
872+
:c:func:`PyFrame_GetBuiltins`, :c:func:`PyFrame_GetGenerator`,
873+
:c:func:`PyFrame_GetGlobals`.
871874

872875
Porting to Python 3.11
873876
----------------------
@@ -985,13 +988,13 @@ Porting to Python 3.11
985988

986989
* ``f_back``: use :c:func:`PyFrame_GetBack`.
987990
* ``f_blockstack``: removed.
988-
* ``f_builtins``: use ``PyObject_GetAttrString((PyObject*)frame, "f_builtins")``.
991+
* ``f_builtins``: use :c:func:`PyFrame_GetBuiltins`.
989992
* ``f_code``: use :c:func:`PyFrame_GetCode`.
990-
* ``f_gen``: removed.
991-
* ``f_globals``: use ``PyObject_GetAttrString((PyObject*)frame, "f_globals")``.
993+
* ``f_gen``: use :c:func:`PyFrame_GetGenerator`.
994+
* ``f_globals``: use :c:func:`PyFrame_GetGlobals`.
992995
* ``f_iblock``: removed.
993996
* ``f_lasti``: use ``PyObject_GetAttrString((PyObject*)frame, "f_lasti")``.
994-
Code using ``f_lasti`` with ``PyCode_Addr2Line()`` must use
997+
Code using ``f_lasti`` with ``PyCode_Addr2Line()`` should use
995998
:c:func:`PyFrame_GetLineNumber` instead.
996999
* ``f_lineno``: use :c:func:`PyFrame_GetLineNumber`
9971000
* ``f_locals``: use :c:func:`PyFrame_GetLocals`.

Include/cpython/frameobject.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,8 @@ PyAPI_FUNC(void) PyFrame_FastToLocals(PyFrameObject *);
2424

2525
PyAPI_FUNC(PyFrameObject *) PyFrame_GetBack(PyFrameObject *frame);
2626
PyAPI_FUNC(PyObject *) PyFrame_GetLocals(PyFrameObject *frame);
27+
28+
PyAPI_FUNC(PyObject *) PyFrame_GetGlobals(PyFrameObject *frame);
29+
PyAPI_FUNC(PyObject *) PyFrame_GetBuiltins(PyFrameObject *frame);
30+
31+
PyAPI_FUNC(PyObject *) PyFrame_GetGenerator(PyFrameObject *frame);

Lib/test/test_capi.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,5 +1087,25 @@ class Subclass(BaseException, self.module.StateAccessType):
10871087
self.assertIs(Subclass().get_defining_module(), self.module)
10881088

10891089

1090+
class Test_FrameAPI(unittest.TestCase):
1091+
1092+
def getframe(self):
1093+
return sys._getframe()
1094+
1095+
def getgenframe(self):
1096+
yield sys._getframe()
1097+
1098+
def test_frame_getters(self):
1099+
frame = self.getframe()
1100+
self.assertEquals(frame.f_locals, _testcapi.frame_getlocals(frame))
1101+
self.assertIs(frame.f_globals, _testcapi.frame_getglobals(frame))
1102+
self.assertIs(frame.f_builtins, _testcapi.frame_getbuiltins(frame))
1103+
1104+
def test_frame_get_generator(self):
1105+
gen = self.getgenframe()
1106+
frame = next(gen)
1107+
self.assertIs(gen, _testcapi.frame_getgenerator(frame))
1108+
1109+
10901110
if __name__ == "__main__":
10911111
unittest.main()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add ``PyFrame_GetBuiltins``, ``PyFrame_GetGenerator`` and
2+
``PyFrame_GetGlobals`` C-API functions to access frame object attributes
3+
safely from C code.

Modules/_testcapimodule.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5853,6 +5853,46 @@ test_float_unpack(PyObject *self, PyObject *args)
58535853
return PyFloat_FromDouble(d);
58545854
}
58555855

5856+
static PyObject *
5857+
frame_getlocals(PyObject *self, PyObject *frame)
5858+
{
5859+
if (!PyFrame_Check(frame)) {
5860+
PyErr_SetString(PyExc_TypeError, "argument must be a frame");
5861+
return NULL;
5862+
}
5863+
return PyFrame_GetLocals((PyFrameObject *)frame);
5864+
}
5865+
5866+
static PyObject *
5867+
frame_getglobals(PyObject *self, PyObject *frame)
5868+
{
5869+
if (!PyFrame_Check(frame)) {
5870+
PyErr_SetString(PyExc_TypeError, "argument must be a frame");
5871+
return NULL;
5872+
}
5873+
return PyFrame_GetGlobals((PyFrameObject *)frame);
5874+
}
5875+
5876+
static PyObject *
5877+
frame_getgenerator(PyObject *self, PyObject *frame)
5878+
{
5879+
if (!PyFrame_Check(frame)) {
5880+
PyErr_SetString(PyExc_TypeError, "argument must be a frame");
5881+
return NULL;
5882+
}
5883+
return PyFrame_GetGenerator((PyFrameObject *)frame);
5884+
}
5885+
5886+
static PyObject *
5887+
frame_getbuiltins(PyObject *self, PyObject *frame)
5888+
{
5889+
if (!PyFrame_Check(frame)) {
5890+
PyErr_SetString(PyExc_TypeError, "argument must be a frame");
5891+
return NULL;
5892+
}
5893+
return PyFrame_GetBuiltins((PyFrameObject *)frame);
5894+
}
5895+
58565896

58575897
static PyObject *negative_dictoffset(PyObject *, PyObject *);
58585898
static PyObject *test_buildvalue_issue38913(PyObject *, PyObject *);
@@ -6142,6 +6182,10 @@ static PyMethodDef TestMethods[] = {
61426182
{"test_tstate_capi", test_tstate_capi, METH_NOARGS, NULL},
61436183
{"float_pack", test_float_pack, METH_VARARGS, NULL},
61446184
{"float_unpack", test_float_unpack, METH_VARARGS, NULL},
6185+
{"frame_getlocals", frame_getlocals, METH_O, NULL},
6186+
{"frame_getglobals", frame_getglobals, METH_O, NULL},
6187+
{"frame_getgenerator", frame_getgenerator, METH_O, NULL},
6188+
{"frame_getbuiltins", frame_getbuiltins, METH_O, NULL},
61456189
{NULL, NULL} /* sentinel */
61466190
};
61476191

Objects/frameobject.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,6 +1134,28 @@ PyFrame_GetLocals(PyFrameObject *frame)
11341134
return frame_getlocals(frame, NULL);
11351135
}
11361136

1137+
PyObject*
1138+
PyFrame_GetGlobals(PyFrameObject *frame)
1139+
{
1140+
return frame_getglobals(frame, NULL);
1141+
}
1142+
1143+
PyObject*
1144+
PyFrame_GetBuiltins(PyFrameObject *frame)
1145+
{
1146+
return frame_getbuiltins(frame, NULL);
1147+
}
1148+
1149+
PyObject *
1150+
PyFrame_GetGenerator(PyFrameObject *frame)
1151+
{
1152+
if (frame->f_frame->owner != FRAME_OWNED_BY_GENERATOR) {
1153+
return NULL;
1154+
}
1155+
PyGenObject *gen = _PyFrame_GetGenerator(frame->f_frame);
1156+
return Py_NewRef(gen);
1157+
}
1158+
11371159
PyObject*
11381160
_PyEval_BuiltinsFromGlobals(PyThreadState *tstate, PyObject *globals)
11391161
{

0 commit comments

Comments
 (0)