[Python-checkins] gh-91248: Add PyFrame_GetVar() function (#95712)

vstinner webhook-mailer at python.org
Tue Nov 8 11:40:34 EST 2022


https://github.com/python/cpython/commit/4d5fcca273b24a5566f1507758e5aae60cdf8a98
commit: 4d5fcca273b24a5566f1507758e5aae60cdf8a98
branch: main
author: Victor Stinner <vstinner at python.org>
committer: vstinner <vstinner at python.org>
date: 2022-11-08T17:40:27+01:00
summary:

gh-91248: Add PyFrame_GetVar() function (#95712)

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.

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

diff --git a/Doc/c-api/frame.rst b/Doc/c-api/frame.rst
index 46ce700abf14..4a062dd8623c 100644
--- a/Doc/c-api/frame.rst
+++ b/Doc/c-api/frame.rst
@@ -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`).
diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst
index cf71aab1d8a3..7ebfc5537c7a 100644
--- a/Doc/whatsnew/3.12.rst
+++ b/Doc/whatsnew/3.12.rst
@@ -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
 ----------------------
 
diff --git a/Include/cpython/pyframe.h b/Include/cpython/pyframe.h
index 1dc634ccee9a..6ec292718aff 100644
--- a/Include/cpython/pyframe.h
+++ b/Include/cpython/pyframe.h
@@ -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);
diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py
index 213b6d4feb63..ea4c9de47d73 100644
--- a/Lib/test/test_capi.py
+++ b/Lib/test/test_capi.py
@@ -1677,27 +1677,6 @@ 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.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):
diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py
index 4b5bb7f94ac4..ada966683924 100644
--- a/Lib/test/test_frame.py
+++ b/Lib/test/test_frame.py
@@ -5,6 +5,10 @@
 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 @@ def f():
                 gc.enable()
 
 
+ at 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
index 000000000000..6521f57b8fe0
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2022-08-05-15-26-12.gh-issue-91248.ujirJJ.rst	
@@ -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.
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 1624a93ec3f3..0615c7352ca0 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -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},
diff --git a/Objects/frameobject.c b/Objects/frameobject.c
index 4b4be382d943..6337501cfca8 100644
--- a/Objects/frameobject.c
+++ b/Objects/frameobject.c
@@ -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;
+}



More information about the Python-checkins mailing list