summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMark Shannon <mark@hotpy.org>2022-03-31 16:13:25 (GMT)
committerGitHub <noreply@github.com>2022-03-31 16:13:25 (GMT)
commit74b95d86e0f14603f878c4df3133bc8a93f8f80a (patch)
treebdd3f8b2a671bfa2458c1a4e34098ff70fa6e36b
parent44e915028d75f7cef141aa1aada962465a5907d6 (diff)
downloadcpython-74b95d86e0f14603f878c4df3133bc8a93f8f80a.zip
cpython-74b95d86e0f14603f878c4df3133bc8a93f8f80a.tar.gz
cpython-74b95d86e0f14603f878c4df3133bc8a93f8f80a.tar.bz2
bpo-40421: Add missing getters for frame object attributes to C-API. (GH-32114)
-rw-r--r--Doc/c-api/frame.rst35
-rw-r--r--Doc/whatsnew/3.11.rst11
-rw-r--r--Include/cpython/frameobject.h5
-rw-r--r--Lib/test/test_capi.py20
-rw-r--r--Misc/NEWS.d/next/C API/2022-03-25-13-40-46.bpo-40421.wJREl2.rst3
-rw-r--r--Modules/_testcapimodule.c44
-rw-r--r--Objects/frameobject.c22
7 files changed, 136 insertions, 4 deletions
diff --git a/Doc/c-api/frame.rst b/Doc/c-api/frame.rst
index 0c11bc1..6d265e4 100644
--- a/Doc/c-api/frame.rst
+++ b/Doc/c-api/frame.rst
@@ -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.
@@ -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`).
diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst
index 1bd9587..16715c3 100644
--- a/Doc/whatsnew/3.11.rst
+++ b/Doc/whatsnew/3.11.rst
@@ -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
----------------------
@@ -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`.
diff --git a/Include/cpython/frameobject.h b/Include/cpython/frameobject.h
index d54d365..ffeb8bd 100644
--- a/Include/cpython/frameobject.h
+++ b/Include/cpython/frameobject.h
@@ -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);
diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py
index d961543..238acf9 100644
--- a/Lib/test/test_capi.py
+++ b/Lib/test/test_capi.py
@@ -1087,5 +1087,25 @@ class Test_ModuleStateAccess(unittest.TestCase):
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()
diff --git a/Misc/NEWS.d/next/C API/2022-03-25-13-40-46.bpo-40421.wJREl2.rst b/Misc/NEWS.d/next/C API/2022-03-25-13-40-46.bpo-40421.wJREl2.rst
new file mode 100644
index 0000000..95b7b69
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2022-03-25-13-40-46.bpo-40421.wJREl2.rst
@@ -0,0 +1,3 @@
+Add ``PyFrame_GetBuiltins``, ``PyFrame_GetGenerator`` and
+``PyFrame_GetGlobals`` C-API functions to access frame object attributes
+safely from C code.
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 019c2b8..759656a 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -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 *);
@@ -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 */
};
diff --git a/Objects/frameobject.c b/Objects/frameobject.c
index d4993104..581de22 100644
--- a/Objects/frameobject.c
+++ b/Objects/frameobject.c
@@ -1135,6 +1135,28 @@ PyFrame_GetLocals(PyFrameObject *frame)
}
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)
{
PyObject *builtins = PyDict_GetItemWithError(globals, &_Py_ID(__builtins__));