]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-91248: Add PyFrame_GetVar() function (#95712)
authorVictor Stinner <vstinner@python.org>
Tue, 8 Nov 2022 16:40:27 +0000 (17:40 +0100)
committerGitHub <noreply@github.com>
Tue, 8 Nov 2022 16:40:27 +0000 (17:40 +0100)
Add PyFrame_GetVar() and PyFrame_GetVarString() functions to get a
frame variable by its name.

Move PyFrameObject C API tests from test_capi to test_frame.

Doc/c-api/frame.rst
Doc/whatsnew/3.12.rst
Include/cpython/pyframe.h
Lib/test/test_capi.py
Lib/test/test_frame.py
Misc/NEWS.d/next/C API/2022-08-05-15-26-12.gh-issue-91248.ujirJJ.rst [new file with mode: 0644]
Modules/_testcapimodule.c
Objects/frameobject.c

index 46ce700abf14748533bbb7b2ac6764f2f9cba4e8..4a062dd8623c479c17ca418ca52f32612c491cad 100644 (file)
@@ -79,6 +79,25 @@ See also :ref:`Reflection <reflection>`.
    .. versionadded:: 3.11
 
 
+.. c:function:: PyObject* PyFrame_GetVar(PyFrameObject *frame, PyObject *name)
+
+   Get the variable *name* of *frame*.
+
+   * Return a :term:`strong reference` to the variable value on success.
+   * Raise :exc:`NameError` and return ``NULL`` if the variable does not exist.
+   * Raise an exception and return ``NULL`` on error.
+
+   .. versionadded:: 3.12
+
+
+.. c:function:: PyObject* PyFrame_GetVarString(PyFrameObject *frame, const char *name)
+
+   Similar to :c:func:`PyFrame_GetVar`, but the variable name is a C string
+   encoded in UTF-8.
+
+   .. versionadded:: 3.12
+
+
 .. c:function:: PyObject* PyFrame_GetLocals(PyFrameObject *frame)
 
    Get the *frame*'s ``f_locals`` attribute (:class:`dict`).
index cf71aab1d8a32aa5ce12648a8c02126cd5c1253b..7ebfc5537c7a3d641ec9a8908faa86cd9738f190 100644 (file)
@@ -735,6 +735,10 @@ New Features
   (Contributed by Carl Meyer in :gh:`91051`.)
 
 
+* Add :c:func:`PyFrame_GetVar` and :c:func:`PyFrame_GetVarString` functions to
+  get a frame variable by its name.
+  (Contributed by Victor Stinner in :gh:`91248`.)
+
 Porting to Python 3.12
 ----------------------
 
index 1dc634ccee9a27afd1bd038370f50c867f8fef81..6ec292718aff1ab21cb6762a19f52a40fc2085d7 100644 (file)
@@ -14,4 +14,5 @@ PyAPI_FUNC(PyObject *) PyFrame_GetBuiltins(PyFrameObject *frame);
 
 PyAPI_FUNC(PyObject *) PyFrame_GetGenerator(PyFrameObject *frame);
 PyAPI_FUNC(int) PyFrame_GetLasti(PyFrameObject *frame);
-
+PyAPI_FUNC(PyObject*) PyFrame_GetVar(PyFrameObject *frame, PyObject *name);
+PyAPI_FUNC(PyObject*) PyFrame_GetVarString(PyFrameObject *frame, const char *name);
index 213b6d4feb632266d92c3691ee99364d8b4571bc..ea4c9de47d73def07490a2d99bf7a141e0e219f0 100644 (file)
@@ -1677,27 +1677,6 @@ 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.assertEqual(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))
-        self.assertEqual(frame.f_lasti, _testcapi.frame_getlasti(frame))
-
-    def test_frame_get_generator(self):
-        gen = self.getgenframe()
-        frame = next(gen)
-        self.assertIs(gen, _testcapi.frame_getgenerator(frame))
-
-
 SUFFICIENT_TO_DEOPT_AND_SPECIALIZE = 100
 
 class Test_Pep523API(unittest.TestCase):
index 4b5bb7f94ac4692402e72ac0e9efb64d7407a2a9..ada9666839242641445882c672f00acb38e226c1 100644 (file)
@@ -5,6 +5,10 @@ import textwrap
 import types
 import unittest
 import weakref
+try:
+    import _testcapi
+except ImportError:
+    _testcapi = None
 
 from test import support
 from test.support.script_helper import assert_python_ok
@@ -326,5 +330,36 @@ class TestIncompleteFrameAreInvisible(unittest.TestCase):
                 gc.enable()
 
 
+@unittest.skipIf(_testcapi is None, 'need _testcapi')
+class TestCAPI(unittest.TestCase):
+    def getframe(self):
+        return sys._getframe()
+
+    def test_frame_getters(self):
+        frame = self.getframe()
+        self.assertEqual(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))
+        self.assertEqual(frame.f_lasti, _testcapi.frame_getlasti(frame))
+
+    def test_getvar(self):
+        current_frame = sys._getframe()
+        x = 1
+        self.assertEqual(_testcapi.frame_getvar(current_frame, "x"), 1)
+        self.assertEqual(_testcapi.frame_getvarstring(current_frame, b"x"), 1)
+        with self.assertRaises(NameError):
+            _testcapi.frame_getvar(current_frame, "y")
+        with self.assertRaises(NameError):
+            _testcapi.frame_getvarstring(current_frame, b"y")
+
+    def getgenframe(self):
+        yield sys._getframe()
+
+    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-08-05-15-26-12.gh-issue-91248.ujirJJ.rst b/Misc/NEWS.d/next/C API/2022-08-05-15-26-12.gh-issue-91248.ujirJJ.rst
new file mode 100644 (file)
index 0000000..6521f57
--- /dev/null
@@ -0,0 +1,2 @@
+Add :c:func:`PyFrame_GetVar` and :c:func:`PyFrame_GetVarString` functions to
+get a frame variable by its name. Patch by Victor Stinner.
index 1624a93ec3f3e3d4f8662b584bacb9297fcbf009..0615c7352ca0603f277d458c5ea0f415c326a479 100644 (file)
@@ -5607,6 +5607,38 @@ frame_getlasti(PyObject *self, PyObject *frame)
     return PyLong_FromLong(lasti);
 }
 
+static PyObject *
+test_frame_getvar(PyObject *self, PyObject *args)
+{
+    PyObject *frame, *name;
+    if (!PyArg_ParseTuple(args, "OO", &frame, &name)) {
+        return NULL;
+    }
+    if (!PyFrame_Check(frame)) {
+        PyErr_SetString(PyExc_TypeError, "argument must be a frame");
+        return NULL;
+    }
+
+    return PyFrame_GetVar((PyFrameObject *)frame, name);
+}
+
+static PyObject *
+test_frame_getvarstring(PyObject *self, PyObject *args)
+{
+    PyObject *frame;
+    const char *name;
+    if (!PyArg_ParseTuple(args, "Oy", &frame, &name)) {
+        return NULL;
+    }
+    if (!PyFrame_Check(frame)) {
+        PyErr_SetString(PyExc_TypeError, "argument must be a frame");
+        return NULL;
+    }
+
+    return PyFrame_GetVarString((PyFrameObject *)frame, name);
+}
+
+
 static PyObject *
 eval_get_func_name(PyObject *self, PyObject *func)
 {
@@ -6294,6 +6326,8 @@ static PyMethodDef TestMethods[] = {
     {"frame_getgenerator", frame_getgenerator, METH_O, NULL},
     {"frame_getbuiltins", frame_getbuiltins, METH_O, NULL},
     {"frame_getlasti", frame_getlasti, METH_O, NULL},
+    {"frame_getvar", test_frame_getvar, METH_VARARGS, NULL},
+    {"frame_getvarstring", test_frame_getvarstring, METH_VARARGS, NULL},
     {"eval_get_func_name", eval_get_func_name, METH_O, NULL},
     {"eval_get_func_desc", eval_get_func_desc, METH_O, NULL},
     {"get_feature_macros", get_feature_macros, METH_NOARGS, NULL},
index 4b4be382d94366032e4148384ccf05b28c000b49..6337501cfca8847a8856ff96e1b3c9776c9c603b 100644 (file)
@@ -1430,4 +1430,34 @@ _PyEval_BuiltinsFromGlobals(PyThreadState *tstate, PyObject *globals)
     return _PyEval_GetBuiltins(tstate);
 }
 
+PyObject *
+PyFrame_GetVar(PyFrameObject *frame, PyObject *name)
+{
+    PyObject *locals = PyFrame_GetLocals(frame);
+    if (locals == NULL) {
+        return NULL;
+    }
+    PyObject *value = PyDict_GetItemWithError(locals, name);
+    Py_DECREF(locals);
 
+    if (value == NULL) {
+        if (PyErr_Occurred()) {
+            return NULL;
+        }
+        PyErr_Format(PyExc_NameError, "variable %R does not exist", name);
+        return NULL;
+    }
+    return Py_NewRef(value);
+}
+
+PyObject *
+PyFrame_GetVarString(PyFrameObject *frame, const char *name)
+{
+    PyObject *name_obj = PyUnicode_FromString(name);
+    if (name_obj == NULL) {
+        return NULL;
+    }
+    PyObject *value = PyFrame_GetVar(frame, name_obj);
+    Py_DECREF(name_obj);
+    return value;
+}