8000 gh-91248: Add PyFrame_GetVar() function by vstinner · Pull Request #95712 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-91248: Add PyFrame_GetVar() function #95712

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Doc/c-api/frame.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`).
Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,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
----------------------

Expand Down
3 changes: 2 additions & 1 deletion Include/cpython/pyframe.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
21 changes: 0 additions & 21 deletions Lib/test/test_capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -1490,27 +1490,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):
Expand Down
35 changes: 35 additions & 0 deletions Lib/test/test_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -326,5 +330,36 @@ def f():
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(curr 8000 ent_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()
Original file line number Diff line number Diff line change
@@ -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.
34 changes: 34 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -5573,6 +5573,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)
{
Expand Down Expand Up @@ -6225,6 +6257,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},
{"get_feature_macros", get_feature_macros, METH_NOARGS, NULL},
{"test_code_api", test_code_api, METH_NOARGS, NULL},
Expand Down
30 changes: 30 additions & 0 deletions Objects/frameobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1438,4 +1438,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;
}
0