diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index 46bc18cff86d5a..e729904ff2c4cc 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -156,6 +156,7 @@ extern PyObject* _Py_MakeCoro(PyFunctionObject *func); and asynchronous exception */ extern int _Py_HandlePending(PyThreadState *tstate); +extern PyObject * _PyEval_GetFrameLocals(void); #ifdef __cplusplus diff --git a/Include/internal/pycore_frame.h b/Include/internal/pycore_frame.h index 952a96009dda6c..efc19e33ec5dc5 100644 --- a/Include/internal/pycore_frame.h +++ b/Include/internal/pycore_frame.h @@ -234,6 +234,9 @@ _PyFrame_ClearExceptCode(_PyInterpreterFrame * frame); int _PyFrame_Traverse(_PyInterpreterFrame *frame, visitproc visit, void *arg); +PyObject * +_PyFrame_GetLocals(_PyInterpreterFrame *frame, int include_hidden); + int _PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame); diff --git a/Lib/test/test_listcomps.py b/Lib/test/test_listcomps.py index c2cf058c321fa5..9f28ced32bd26c 100644 --- a/Lib/test/test_listcomps.py +++ b/Lib/test/test_listcomps.py @@ -539,6 +539,28 @@ def b(): self._check_in_scopes(code, {"x": True, "y": ["b"]}, scopes=["function"]) self._check_in_scopes(code, raises=NameError, scopes=["class"]) + def test_iter_var_available_in_locals(self): + code = """ + l = [1, 2] + y = 0 + items = [locals()["x"] for x in l] + items2 = [vars()["x"] for x in l] + items3 = [("x" in dir()) for x in l] + items4 = [eval("x") for x in l] + # x is available, and does not overwrite y + [exec("y = x") for x in l] + """ + self._check_in_scopes( + code, + { + "items": [1, 2], + "items2": [1, 2], + "items3": [True, True], + "items4": [1, 2], + "y": 0 + } + ) + __test__ = {'doctests' : doctests} diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-06-12-16-38-31.gh-issue-105340._jRHXe.rst b/Misc/NEWS.d/next/Core and Builtins/2023-06-12-16-38-31.gh-issue-105340._jRHXe.rst new file mode 100644 index 00000000000000..f6d4fa8fc4d74e --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-06-12-16-38-31.gh-issue-105340._jRHXe.rst @@ -0,0 +1,2 @@ +Include the comprehension iteration variable in ``locals()`` inside a +module- or class-scope comprehension. diff --git a/Objects/frameobject.c b/Objects/frameobject.c index 0158d72d6b4f98..18820551a0547e 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -1200,15 +1200,28 @@ frame_get_var(_PyInterpreterFrame *frame, PyCodeObject *co, int i, return 1; } -int -_PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame) + +PyObject * +_PyFrame_GetLocals(_PyInterpreterFrame *frame, int include_hidden) { /* Merge fast locals into f->f_locals */ PyObject *locals = frame->f_locals; if (locals == NULL) { locals = frame->f_locals = PyDict_New(); if (locals == NULL) { - return -1; + return NULL; + } + } + PyObject *hidden = NULL; + + /* If include_hidden, "hidden" fast locals (from inlined comprehensions in + module/class scopes) will be included in the returned dict, but not in + frame->f_locals; the returned dict will be a modified copy. Non-hidden + locals will still be updated in frame->f_locals. */ + if (include_hidden) { + hidden = PyDict_New(); + if (hidden == NULL) { + return NULL; } } @@ -1224,6 +1237,11 @@ _PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame) PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i); _PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i); if (kind & CO_FAST_HIDDEN) { + if (include_hidden && value != NULL) { + if (PyObject_SetItem(hidden, name, value) != 0) { + goto error; + } + } continue; } if (value == NULL) { @@ -1232,16 +1250,53 @@ _PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame) PyErr_Clear(); } else { - return -1; + goto error; } } } else { if (PyObject_SetItem(locals, name, value) != 0) { - return -1; + goto error; } } } + + if (include_hidden && PyDict_Size(hidden)) { + PyObject *innerlocals = PyDict_New(); + if (innerlocals == NULL) { + goto error; + } + if (PyDict_Merge(innerlocals, locals, 1) != 0) { + Py_DECREF(innerlocals); + goto error; + } + if (PyDict_Merge(innerlocals, hidden, 1) != 0) { + Py_DECREF(innerlocals); + goto error; + } + locals = innerlocals; + } + else { + Py_INCREF(locals); + } + Py_CLEAR(hidden); + + return locals; + + error: + Py_XDECREF(hidden); + return NULL; +} + + +int +_PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame) +{ + PyObject *locals = _PyFrame_GetLocals(frame, 0); + if (locals == NULL) { + return -1; + } + Py_DECREF(locals); return 0; } diff --git a/Objects/object.c b/Objects/object.c index b20e87ef3fb23d..c27b13e9e0c31a 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1690,13 +1690,15 @@ _dir_locals(void) PyObject *names; PyObject *locals; - locals = PyEval_GetLocals(); + locals = _PyEval_GetFrameLocals(); if (locals == NULL) return NULL; names = PyMapping_Keys(locals); - if (!names) + Py_DECREF(locals); + if (!names) { return NULL; + } if (!PyList_Check(names)) { PyErr_Format(PyExc_TypeError, "dir(): expected keys() of locals to be a list, " @@ -1708,7 +1710,6 @@ _dir_locals(void) Py_DECREF(names); return NULL; } - /* the locals don't need to be DECREF'd */ return names; } diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 9fe0067daa678c..49efafc07f4245 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -908,7 +908,7 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals, PyObject *locals) /*[clinic end generated code: output=0a0824aa70093116 input=11ee718a8640e527]*/ { - PyObject *result, *source_copy; + PyObject *result = NULL, *source_copy; const char *str; if (locals != Py_None && !PyMapping_Check(locals)) { @@ -924,19 +924,25 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals, if (globals == Py_None) { globals = PyEval_GetGlobals(); if (locals == Py_None) { - locals = PyEval_GetLocals(); + locals = _PyEval_GetFrameLocals(); if (locals == NULL) return NULL; } + else { + Py_INCREF(locals); + } } else if (locals == Py_None) - locals = globals; + locals = Py_NewRef(globals); + else { + Py_INCREF(locals); + } if (globals == NULL || locals == NULL) { PyErr_SetString(PyExc_TypeError, "eval must be given globals and locals " "when called without a frame"); - return NULL; + goto error; } int r = PyDict_Contains(globals, &_Py_ID(__builtins__)); @@ -944,34 +950,38 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals, r = PyDict_SetItem(globals, &_Py_ID(__builtins__), PyEval_GetBuiltins()); } if (r < 0) { - return NULL; + goto error; } if (PyCode_Check(source)) { if (PySys_Audit("exec", "O", source) < 0) { - return NULL; + goto error; } if (PyCode_GetNumFree((PyCodeObject *)source) > 0) { PyErr_SetString(PyExc_TypeError, "code object passed to eval() may not contain free variables"); - return NULL; + goto error; } - return PyEval_EvalCode(source, globals, locals); + result = PyEval_EvalCode(source, globals, locals); } + else { + PyCompilerFlags cf = _PyCompilerFlags_INIT; + cf.cf_flags = PyCF_SOURCE_IS_UTF8; + str = _Py_SourceAsString(source, "eval", "string, bytes or code", &cf, &source_copy); + if (str == NULL) + goto error; - PyCompilerFlags cf = _PyCompilerFlags_INIT; - cf.cf_flags = PyCF_SOURCE_IS_UTF8; - str = _Py_SourceAsString(source, "eval", "string, bytes or code", &cf, &source_copy); - if (str == NULL) - return NULL; + while (*str == ' ' || *str == '\t') + str++; - while (*str == ' ' || *str == '\t') - str++; + (void)PyEval_MergeCompilerFlags(&cf); + result = PyRun_StringFlags(str, Py_eval_input, globals, locals, &cf); + Py_XDECREF(source_copy); + } - (void)PyEval_MergeCompilerFlags(&cf); - result = PyRun_StringFlags(str, Py_eval_input, globals, locals, &cf); - Py_XDECREF(source_copy); + error: + Py_XDECREF(locals); return result; } @@ -1006,36 +1016,43 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals, if (globals == Py_None) { globals = PyEval_GetGlobals(); if (locals == Py_None) { - locals = PyEval_GetLocals(); + locals = _PyEval_GetFrameLocals(); if (locals == NULL) return NULL; } + else { + Py_INCREF(locals); + } if (!globals || !locals) { PyErr_SetString(PyExc_SystemError, "globals and locals cannot be NULL"); return NULL; } } - else if (locals == Py_None) - locals = globals; + else if (locals == Py_None) { + locals = Py_NewRef(globals); + } + else { + Py_INCREF(locals); + } if (!PyDict_Check(globals)) { PyErr_Format(PyExc_TypeError, "exec() globals must be a dict, not %.100s", Py_TYPE(globals)->tp_name); - return NULL; + goto error; } if (!PyMapping_Check(locals)) { PyErr_Format(PyExc_TypeError, "locals must be a mapping or None, not %.100s", Py_TYPE(locals)->tp_name); - return NULL; + goto error; } int r = PyDict_Contains(globals, &_Py_ID(__builtins__)); if (r == 0) { r = PyDict_SetItem(globals, &_Py_ID(__builtins__), PyEval_GetBuiltins()); } if (r < 0) { - return NULL; + goto error; } if (closure == Py_None) { @@ -1048,7 +1065,7 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals, if (closure) { PyErr_SetString(PyExc_TypeError, "cannot use a closure with this code object"); - return NULL; + goto error; } } else { int closure_is_ok = @@ -1068,12 +1085,12 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals, PyErr_Format(PyExc_TypeError, "code object requires a closure of exactly length %zd", num_free); - return NULL; + goto error; } } if (PySys_Audit("exec", "O", source) < 0) { - return NULL; + goto error; } if (!closure) { @@ -1100,7 +1117,7 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals, "string, bytes or code", &cf, &source_copy); if (str == NULL) - return NULL; + goto error; if (PyEval_MergeCompilerFlags(&cf)) v = PyRun_StringFlags(str, Py_file_input, globals, locals, &cf); @@ -1109,9 +1126,14 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals, Py_XDECREF(source_copy); } if (v == NULL) - return NULL; + goto error; + Py_DECREF(locals); Py_DECREF(v); Py_RETURN_NONE; + + error: + Py_XDECREF(locals); + return NULL; } @@ -1721,10 +1743,7 @@ static PyObject * builtin_locals_impl(PyObject *module) /*[clinic end generated code: output=b46c94015ce11448 input=7874018d478d5c4b]*/ { - PyObject *d; - - d = PyEval_GetLocals(); - return Py_XNewRef(d); + return _PyEval_GetFrameLocals(); } @@ -2442,7 +2461,7 @@ builtin_vars_impl(PyObject *module, PyObject *object) PyObject *d; if (object == NULL) { - d = Py_XNewRef(PyEval_GetLocals()); + d = _PyEval_GetFrameLocals(); } else { if (_PyObject_LookupAttr(object, &_Py_ID(__dict__), &d) == 0) { diff --git a/Python/ceval.c b/Python/ceval.c index 9bcb83f9c993cf..6714229fd07846 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2261,6 +2261,19 @@ PyEval_GetLocals(void) return locals; } +PyObject * +_PyEval_GetFrameLocals(void) +{ + PyThreadState *tstate = _PyThreadState_GET(); + _PyInterpreterFrame *current_frame = _PyThreadState_GetFrame(tstate); + if (current_frame == NULL) { + _PyErr_SetString(tstate, PyExc_SystemError, "frame does not exist"); + return NULL; + } + + return _PyFrame_GetLocals(current_frame, 1); +} + PyObject * PyEval_GetGlobals(void) {