]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-40421: Add missing getters for frame object attributes to C-API. (GH-32114)
authorMark Shannon <mark@hotpy.org>
Thu, 31 Mar 2022 16:13:25 +0000 (17:13 +0100)
committerGitHub <noreply@github.com>
Thu, 31 Mar 2022 16:13:25 +0000 (17:13 +0100)
Doc/c-api/frame.rst
Doc/whatsnew/3.11.rst
Include/cpython/frameobject.h
Lib/test/test_capi.py
Misc/NEWS.d/next/C API/2022-03-25-13-40-46.bpo-40421.wJREl2.rst [new file with mode: 0644]
Modules/_testcapimodule.c
Objects/frameobject.c

index 0c11bc163b417fee8bfcde2d041187871fc8d953..6d265e45659f907c565747aeb03696a0e976e571 100644 (file)
@@ -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`).
index 1bd958724f3be770334c137acde147745ea5a344..16715c32502e403b190caf9f315b10affe4821c1 100644 (file)
@@ -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`.
index d54d3652a0dbc85c4a195fad7bba47ac5f687e47..ffeb8bd04a46f4b6bf8e3e0607dc3547edb61f91 100644 (file)
@@ -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);
index d9615430327a408c2f1f15aefe934ed65b6fcde7..238acf94526a3fc403d823eb646540a35d048ba6 100644 (file)
@@ -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 (file)
index 0000000..95b7b69
--- /dev/null
@@ -0,0 +1,3 @@
+Add ``PyFrame_GetBuiltins``, ``PyFrame_GetGenerator`` and
+``PyFrame_GetGlobals`` C-API functions to access frame object attributes
+safely from C code.
index 019c2b85b6156a082bdc518be3e2aa68720b3ea1..759656ae1f8a1516ed641fb41f81359733d88d8d 100644 (file)
@@ -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 */
 };
 
index d49931048a6252035cf0c3852cb8720b14bae1e7..581de22587219b2c6637353caac3cde66b90f3b0 100644 (file)
@@ -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)
 {