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.
This commit is contained in:
Victor Stinner 2022-11-08 17:40:27 +01:00 committed by GitHub
parent acf4d5d5bd
commit 4d5fcca273
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 126 additions and 22 deletions

View 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`).

View 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
----------------------

View 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);

View 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):

View 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()

View File

@ -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.

View 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},

View 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;
}