[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