Skip to content

Commit 4d5fcca

Browse files
authored
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.
1 parent acf4d5d commit 4d5fcca

File tree

8 files changed

+126
-22
lines changed

8 files changed

+126
-22
lines changed

Doc/c-api/frame.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,25 @@ See also :ref:`Reflection <reflection>`.
7979
.. versionadded:: 3.11
8080
8181
82+
.. c:function:: PyObject* PyFrame_GetVar(PyFrameObject *frame, PyObject *name)
83+
84+
Get the variable *name* of *frame*.
85+
86+
* Return a :term:`strong reference` to the variable value on success.
87+
* Raise :exc:`NameError` and return ``NULL`` if the variable does not exist.
88+
* Raise an exception and return ``NULL`` on error.
89+
90+
.. versionadded:: 3.12
91+
92+
93+
.. c:function:: PyObject* PyFrame_GetVarString(PyFrameObject *frame, const char *name)
94+
95+
Similar to :c:func:`PyFrame_GetVar`, but the variable name is a C string
96+
encoded in UTF-8.
97+
98+
.. versionadded:: 3.12
99+
100+
82101
.. c:function:: PyObject* PyFrame_GetLocals(PyFrameObject *frame)
83102
84103
Get the *frame*'s ``f_locals`` attribute (:class:`dict`).

Doc/whatsnew/3.12.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,10 @@ New Features
735735
(Contributed by Carl Meyer in :gh:`91051`.)
736736

737737

738+
* Add :c:func:`PyFrame_GetVar` and :c:func:`PyFrame_GetVarString` functions to
739+
get a frame variable by its name.
740+
(Contributed by Victor Stinner in :gh:`91248`.)
741+
738742
Porting to Python 3.12
739743
----------------------
740744

Include/cpython/pyframe.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ PyAPI_FUNC(PyObject *) PyFrame_GetBuiltins(PyFrameObject *frame);
1414

1515
PyAPI_FUNC(PyObject *) PyFrame_GetGenerator(PyFrameObject *frame);
1616
PyAPI_FUNC(int) PyFrame_GetLasti(PyFrameObject *frame);
17-
17+
PyAPI_FUNC(PyObject*) PyFrame_GetVar(PyFrameObject *frame, PyObject *name);
18+
PyAPI_FUNC(PyObject*) PyFrame_GetVarString(PyFrameObject *frame, const char *name);

Lib/test/test_capi.py

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1677,27 +1677,6 @@ class Subclass(BaseException, self.module.StateAccessType):
16771677
self.assertIs(Subclass().get_defining_module(), self.module)
16781678

16791679

1680-
class Test_FrameAPI(unittest.TestCase):
1681-
1682-
def getframe(self):
1683-
return sys._getframe()
1684-
1685-
def getgenframe(self):
1686-
yield sys._getframe()
1687-
1688-
def test_frame_getters(self):
1689-
frame = self.getframe()
1690-
self.assertEqual(frame.f_locals, _testcapi.frame_getlocals(frame))
1691-
self.assertIs(frame.f_globals, _testcapi.frame_getglobals(frame))
1692-
self.assertIs(frame.f_builtins, _testcapi.frame_getbuiltins(frame))
1693-
self.assertEqual(frame.f_lasti, _testcapi.frame_getlasti(frame))
1694-
1695-
def test_frame_get_generator(self):
1696-
gen = self.getgenframe()
1697-
frame = next(gen)
1698-
self.assertIs(gen, _testcapi.frame_getgenerator(frame))
1699-
1700-
17011680
SUFFICIENT_TO_DEOPT_AND_SPECIALIZE = 100
17021681

17031682
class Test_Pep523API(unittest.TestCase):

Lib/test/test_frame.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
import types
66
import unittest
77
import weakref
8+
try:
9+
import _testcapi
10+
except ImportError:
11+
_testcapi = None
812

913
from test import support
1014
from test.support.script_helper import assert_python_ok
@@ -326,5 +330,36 @@ def f():
326330
gc.enable()
327331

328332

333+
@unittest.skipIf(_testcapi is None, 'need _testcapi')
334+
class TestCAPI(unittest.TestCase):
335+
def getframe(self):
336+
return sys._getframe()
337+
338+
def test_frame_getters(self):
339+
frame = self.getframe()
340+
self.assertEqual(frame.f_locals, _testcapi.frame_getlocals(frame))
341+
self.assertIs(frame.f_globals, _testcapi.frame_getglobals(frame))
342+
self.assertIs(frame.f_builtins, _testcapi.frame_getbuiltins(frame))
343+
self.assertEqual(frame.f_lasti, _testcapi.frame_getlasti(frame))
344+
345+
def test_getvar(self):
346+
current_frame = sys._getframe()
347+
x = 1
348+
self.assertEqual(_testcapi.frame_getvar(current_frame, "x"), 1)
349+
self.assertEqual(_testcapi.frame_getvarstring(current_frame, b"x"), 1)
350+
with self.assertRaises(NameError):
351+
_testcapi.frame_getvar(current_frame, "y")
352+
with self.assertRaises(NameError):
353+
_testcapi.frame_getvarstring(current_frame, b"y")
354+
355+
def getgenframe(self):
356+
yield sys._getframe()
357+
358+
def test_frame_get_generator(self):
359+
gen = self.getgenframe()
360+
frame = next(gen)
361+
self.assertIs(gen, _testcapi.frame_getgenerator(frame))
362+
363+
329364
if __name__ == "__main__":
330365
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :c:func:`PyFrame_GetVar` and :c:func:`PyFrame_GetVarString` functions to
2+
get a frame variable by its name. Patch by Victor Stinner.

Modules/_testcapimodule.c

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5607,6 +5607,38 @@ frame_getlasti(PyObject *self, PyObject *frame)
56075607
return PyLong_FromLong(lasti);
56085608
}
56095609

5610+
static PyObject *
5611+
test_frame_getvar(PyObject *self, PyObject *args)
5612+
{
5613+
PyObject *frame, *name;
5614+
if (!PyArg_ParseTuple(args, "OO", &frame, &name)) {
5615+
return NULL;
5616+
}
5617+
if (!PyFrame_Check(frame)) {
5618+
PyErr_SetString(PyExc_TypeError, "argument must be a frame");
5619+
return NULL;
5620+
}
5621+
5622+
return PyFrame_GetVar((PyFrameObject *)frame, name);
5623+
}
5624+
5625+
static PyObject *
5626+
test_frame_getvarstring(PyObject *self, PyObject *args)
5627+
{
5628+
PyObject *frame;
5629+
const char *name;
5630+
if (!PyArg_ParseTuple(args, "Oy", &frame, &name)) {
5631+
return NULL;
5632+
}
5633+
if (!PyFrame_Check(frame)) {
5634+
PyErr_SetString(PyExc_TypeError, "argument must be a frame");
5635+
return NULL;
5636+
}
5637+
5638+
return PyFrame_GetVarString((PyFrameObject *)frame, name);
5639+
}
5640+
5641+
56105642
static PyObject *
56115643
eval_get_func_name(PyObject *self, PyObject *func)
56125644
{
@@ -6294,6 +6326,8 @@ static PyMethodDef TestMethods[] = {
62946326
{"frame_getgenerator", frame_getgenerator, METH_O, NULL},
62956327
{"frame_getbuiltins", frame_getbuiltins, METH_O, NULL},
62966328
{"frame_getlasti", frame_getlasti, METH_O, NULL},
6329+
{"frame_getvar", test_frame_getvar, METH_VARARGS, NULL},
6330+
{"frame_getvarstring", test_frame_getvarstring, METH_VARARGS, NULL},
62976331
{"eval_get_func_name", eval_get_func_name, METH_O, NULL},
62986332
{"eval_get_func_desc", eval_get_func_desc, METH_O, NULL},
62996333
{"get_feature_macros", get_feature_macros, METH_NOARGS, NULL},

Objects/frameobject.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,4 +1430,34 @@ _PyEval_BuiltinsFromGlobals(PyThreadState *tstate, PyObject *globals)
14301430
return _PyEval_GetBuiltins(tstate);
14311431
}
14321432

1433+
PyObject *
1434+
PyFrame_GetVar(PyFrameObject *frame, PyObject *name)
1435+
{
1436+
PyObject *locals = PyFrame_GetLocals(frame);
1437+
if (locals == NULL) {
1438+
return NULL;
1439+
}
1440+
PyObject *value = PyDict_GetItemWithError(locals, name);
1441+
Py_DECREF(locals);
14331442

1443+
if (value == NULL) {
1444+
if (PyErr_Occurred()) {
1445+
return NULL;
1446+
}
1447+
PyErr_Format(PyExc_NameError, "variable %R does not exist", name);
1448+
return NULL;
1449+
}
1450+
return Py_NewRef(value);
1451+
}
1452+
1453+
PyObject *
1454+
PyFrame_GetVarString(PyFrameObject *frame, const char *name)
1455+
{
1456+
PyObject *name_obj = PyUnicode_FromString(name);
1457+
if (name_obj == NULL) {
1458+
return NULL;
1459+
}
1460+
PyObject *value = PyFrame_GetVar(frame, name_obj);
1461+
Py_DECREF(name_obj);
1462+
return value;
1463+
}

0 commit comments

Comments
 (0)