From c4368ef213a64297954fbca4f5308d69409d6a4b Mon Sep 17 00:00:00 2001 From: Raphael Gaschignard Date: Sat, 17 Jun 2023 16:19:38 +0900 Subject: [PATCH 1/6] Add support for keyword arguments to eval and exec (#105879) --- Lib/test/test_builtin.py | 20 +++++++++ Python/bltinmodule.c | 7 ++- Python/clinic/bltinmodule.c.h | 83 +++++++++++++++++++++++++---------- 3 files changed, 82 insertions(+), 28 deletions(-) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index f5a5c037f1bf1b..942823644e8c73 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -46,6 +46,8 @@ x, y = 1e16, 2.9999 # use temporary values to defeat peephole optimizer HAVE_DOUBLE_ROUNDING = (x + y == 1e16 + 4) +# used as proof of globals being used +A_GLOBAL_VALUE = 123 class Squares: @@ -643,6 +645,11 @@ def __getitem__(self, key): raise ValueError self.assertRaises(ValueError, eval, "foo", {}, X()) + def test_eval_kwargs(self): + data = {"A_GLOBAL_VALUE": 456} + self.assertEqual(eval("globals()['A_GLOBAL_VALUE']", globals=data), 456) + self.assertEqual(eval("globals()['A_GLOBAL_VALUE']", locals=data), 123) + def test_general_eval(self): # Tests that general mappings can be used for the locals argument @@ -736,6 +743,19 @@ def test_exec(self): del l['__builtins__'] self.assertEqual((g, l), ({'a': 1}, {'b': 2})) + def test_exec_kwargs(self): + g = {} + exec('global z\nz = 1', globals=g) + if '__builtins__' in g: + del g['__builtins__'] + self.assertEqual(g, {'z': 1}) + + # if we only set locals, the global assignment will not + # reach this locals dictionary + g = {} + exec('global z\nz = 1', locals=g) + self.assertEqual(g, {}) + def test_exec_globals(self): code = compile("print('Hello World!')", "", "exec") # no builtin function diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 68fe315338a54d..25c86df2501b05 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -891,7 +891,6 @@ eval as builtin_eval source: object globals: object = None locals: object = None - / Evaluate the given source in the context of globals and locals. @@ -905,7 +904,7 @@ If only globals is given, locals defaults to it. static PyObject * builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals, PyObject *locals) -/*[clinic end generated code: output=0a0824aa70093116 input=11ee718a8640e527]*/ +/*[clinic end generated code: output=0a0824aa70093116 input=4cbfb23dd7cbe2a9]*/ { PyObject *result, *source_copy; const char *str; @@ -980,7 +979,6 @@ exec as builtin_exec source: object globals: object = None locals: object = None - / * closure: object(c_default="NULL") = None @@ -998,11 +996,12 @@ when source is a code object requiring exactly that many cellvars. static PyObject * builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals, PyObject *locals, PyObject *closure) -/*[clinic end generated code: output=7579eb4e7646743d input=f13a7e2b503d1d9a]*/ +/*[clinic end generated code: output=7579eb4e7646743d input=99951d1832fece9e]*/ { PyObject *v; if (globals == Py_None) { + globals = PyEval_GetGlobals(); if (locals == Py_None) { locals = PyEval_GetLocals(); diff --git a/Python/clinic/bltinmodule.c.h b/Python/clinic/bltinmodule.c.h index b0d05dde956efd..e9c76aa3a05c63 100644 --- a/Python/clinic/bltinmodule.c.h +++ b/Python/clinic/bltinmodule.c.h @@ -457,7 +457,7 @@ builtin_divmod(PyObject *module, PyObject *const *args, Py_ssize_t nargs) } PyDoc_STRVAR(builtin_eval__doc__, -"eval($module, source, globals=None, locals=None, /)\n" +"eval($module, /, source, globals=None, locals=None)\n" "--\n" "\n" "Evaluate the given source in the context of globals and locals.\n" @@ -469,33 +469,63 @@ PyDoc_STRVAR(builtin_eval__doc__, "If only globals is given, locals defaults to it."); #define BUILTIN_EVAL_METHODDEF \ - {"eval", _PyCFunction_CAST(builtin_eval), METH_FASTCALL, builtin_eval__doc__}, + {"eval", _PyCFunction_CAST(builtin_eval), METH_FASTCALL|METH_KEYWORDS, builtin_eval__doc__}, static PyObject * builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals, PyObject *locals); static PyObject * -builtin_eval(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +builtin_eval(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 3 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(source), &_Py_ID(globals), &_Py_ID(locals), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"source", "globals", "locals", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "eval", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[3]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; PyObject *source; PyObject *globals = Py_None; PyObject *locals = Py_None; - if (!_PyArg_CheckPositional("eval", nargs, 1, 3)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 3, 0, argsbuf); + if (!args) { goto exit; } source = args[0]; - if (nargs < 2) { - goto skip_optional; + if (!noptargs) { + goto skip_optional_pos; } - globals = args[1]; - if (nargs < 3) { - goto skip_optional; + if (args[1]) { + globals = args[1]; + if (!--noptargs) { + goto skip_optional_pos; + } } locals = args[2]; -skip_optional: +skip_optional_pos: return_value = builtin_eval_impl(module, source, globals, locals); exit: @@ -503,7 +533,7 @@ builtin_eval(PyObject *module, PyObject *const *args, Py_ssize_t nargs) } PyDoc_STRVAR(builtin_exec__doc__, -"exec($module, source, globals=None, locals=None, /, *, closure=None)\n" +"exec($module, /, source, globals=None, locals=None, *, closure=None)\n" "--\n" "\n" "Execute the given source in the context of globals and locals.\n" @@ -529,14 +559,14 @@ builtin_exec(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - #define NUM_KEYWORDS 1 + #define NUM_KEYWORDS 4 static struct { PyGC_Head _this_is_not_used; PyObject_VAR_HEAD PyObject *ob_item[NUM_KEYWORDS]; } _kwtuple = { .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) - .ob_item = { &_Py_ID(closure), }, + .ob_item = { &_Py_ID(source), &_Py_ID(globals), &_Py_ID(locals), &_Py_ID(closure), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -545,7 +575,7 @@ builtin_exec(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject # define KWTUPLE NULL #endif // !Py_BUILD_CORE - static const char * const _keywords[] = {"", "", "", "closure", NULL}; + static const char * const _keywords[] = {"source", "globals", "locals", "closure", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, .fname = "exec", @@ -564,17 +594,22 @@ builtin_exec(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject goto exit; } source = args[0]; - if (nargs < 2) { - goto skip_optional_posonly; + if (!noptargs) { + goto skip_optional_pos; } - noptargs--; - globals = args[1]; - if (nargs < 3) { - goto skip_optional_posonly; + if (args[1]) { + globals = args[1]; + if (!--noptargs) { + goto skip_optional_pos; + } } - noptargs--; - locals = args[2]; -skip_optional_posonly: + if (args[2]) { + locals = args[2]; + if (!--noptargs) { + goto skip_optional_pos; + } + } +skip_optional_pos: if (!noptargs) { goto skip_optional_kwonly; } @@ -1406,4 +1441,4 @@ builtin_issubclass(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=ef2f16ece134d62d input=a9049054013a1b77]*/ +/*[clinic end generated code: output=caecaf7f4c2d9f21 input=a9049054013a1b77]*/ From 62cad459b0ea42473813a19692335a36dccb18ed Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sun, 18 Jun 2023 00:27:58 +0000 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2023-06-18-00-27-57.gh-issue-105879.dPw78k.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-06-18-00-27-57.gh-issue-105879.dPw78k.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-06-18-00-27-57.gh-issue-105879.dPw78k.rst b/Misc/NEWS.d/next/Core and Builtins/2023-06-18-00-27-57.gh-issue-105879.dPw78k.rst new file mode 100644 index 00000000000000..cabbe426fff025 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-06-18-00-27-57.gh-issue-105879.dPw78k.rst @@ -0,0 +1 @@ +Allow keyword arguments to be used when calling :meth:`~eval` and :meth:`~exec`. From e86e93f16cf0d00f28a05deacfd8ce583e9ce3b1 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 1 May 2024 09:15:25 -0700 Subject: [PATCH 3/6] Undo unnecessary change --- Python/bltinmodule.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 75ed16f277aa7d..82ddd10a402c9d 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -1047,7 +1047,6 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals, PyObject *v; if (globals == Py_None) { - globals = PyEval_GetGlobals(); if (locals == Py_None) { locals = _PyEval_GetFrameLocals(); From 4cd063529da61625b2735b135788ffc2e87cc5c6 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 1 May 2024 09:21:12 -0700 Subject: [PATCH 4/6] Update docs --- Doc/library/functions.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index e598ef423de497..8aed731ecbcd8a 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -583,9 +583,13 @@ are always available. They are listed here in alphabetical order. Raises an :ref:`auditing event ` ``exec`` with the code object as the argument. Code compilation events may also be raised. + .. versionchanged:: 3.13 + + :func:`!eval` now accepts keyword arguments. + .. index:: pair: built-in function; exec -.. function:: exec(object, globals=None, locals=None, /, *, closure=None) +.. function:: exec(object, globals=None, locals=None, *, closure=None) This function supports dynamic execution of Python code. *object* must be either a string or a code object. If it is a string, the string is parsed as @@ -640,6 +644,10 @@ are always available. They are listed here in alphabetical order. .. versionchanged:: 3.11 Added the *closure* parameter. + .. versionchanged:: 3.13 + + :func:`!eval` now accepts keyword arguments. + .. function:: filter(function, iterable) From b994e9dbcc300d3847b7d9df4c7c481fbf0df7d0 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 1 May 2024 09:24:24 -0700 Subject: [PATCH 5/6] Fix name of parameter --- Doc/library/functions.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index 8aed731ecbcd8a..3ed395fbf9b0e7 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -524,11 +524,11 @@ are always available. They are listed here in alphabetical order. .. _func-eval: -.. function:: eval(expression, globals=None, locals=None) +.. function:: eval(source, globals=None, locals=None) - :param expression: + :param source: A Python expression. - :type expression: :class:`str` | :ref:`code object ` + :type source: :class:`str` | :ref:`code object ` :param globals: The global namespace (default: ``None``). @@ -589,9 +589,9 @@ are always available. They are listed here in alphabetical order. .. index:: pair: built-in function; exec -.. function:: exec(object, globals=None, locals=None, *, closure=None) +.. function:: exec(source, globals=None, locals=None, *, closure=None) - This function supports dynamic execution of Python code. *object* must be + This function supports dynamic execution of Python code. *source* must be either a string or a code object. If it is a string, the string is parsed as a suite of Python statements which is then executed (unless a syntax error occurs). [#]_ If it is a code object, it is simply executed. In all cases, @@ -646,7 +646,7 @@ are always available. They are listed here in alphabetical order. .. versionchanged:: 3.13 - :func:`!eval` now accepts keyword arguments. + :func:`!exec` now accepts keyword arguments. .. function:: filter(function, iterable) From 6172dbf40c7855857c069b4255a0255195a21901 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 1 May 2024 19:15:59 -0700 Subject: [PATCH 6/6] Leave first param pos-only --- Doc/library/functions.rst | 8 ++++---- ...3-06-18-00-27-57.gh-issue-105879.dPw78k.rst | 3 ++- Python/bltinmodule.c | 6 ++++-- Python/clinic/bltinmodule.c.h | 18 +++++++++--------- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index 3ed395fbf9b0e7..4c7bc28d605700 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -524,7 +524,7 @@ are always available. They are listed here in alphabetical order. .. _func-eval: -.. function:: eval(source, globals=None, locals=None) +.. function:: eval(source, /, globals=None, locals=None) :param source: A Python expression. @@ -585,11 +585,11 @@ are always available. They are listed here in alphabetical order. .. versionchanged:: 3.13 - :func:`!eval` now accepts keyword arguments. + The *globals* and *locals* arguments can now be passed as keywords. .. index:: pair: built-in function; exec -.. function:: exec(source, globals=None, locals=None, *, closure=None) +.. function:: exec(source, /, globals=None, locals=None, *, closure=None) This function supports dynamic execution of Python code. *source* must be either a string or a code object. If it is a string, the string is parsed as @@ -646,7 +646,7 @@ are always available. They are listed here in alphabetical order. .. versionchanged:: 3.13 - :func:`!exec` now accepts keyword arguments. + The *globals* and *locals* arguments can now be passed as keywords. .. function:: filter(function, iterable) diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-06-18-00-27-57.gh-issue-105879.dPw78k.rst b/Misc/NEWS.d/next/Core and Builtins/2023-06-18-00-27-57.gh-issue-105879.dPw78k.rst index cabbe426fff025..e666688d09cb5c 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2023-06-18-00-27-57.gh-issue-105879.dPw78k.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2023-06-18-00-27-57.gh-issue-105879.dPw78k.rst @@ -1 +1,2 @@ -Allow keyword arguments to be used when calling :meth:`~eval` and :meth:`~exec`. +Allow the *globals* and *locals* arguments to :func:`exec` +and :func:`eval` to be passed as keywords. diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 82ddd10a402c9d..722353ebcbfc3d 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -925,6 +925,7 @@ builtin_divmod_impl(PyObject *module, PyObject *x, PyObject *y) eval as builtin_eval source: object + / globals: object = None locals: object = None @@ -940,7 +941,7 @@ If only globals is given, locals defaults to it. static PyObject * builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals, PyObject *locals) -/*[clinic end generated code: output=0a0824aa70093116 input=4cbfb23dd7cbe2a9]*/ +/*[clinic end generated code: output=0a0824aa70093116 input=7c7bce5299a89062]*/ { PyObject *result = NULL, *source_copy; const char *str; @@ -1023,6 +1024,7 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals, exec as builtin_exec source: object + / globals: object = None locals: object = None * @@ -1042,7 +1044,7 @@ when source is a code object requiring exactly that many cellvars. static PyObject * builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals, PyObject *locals, PyObject *closure) -/*[clinic end generated code: output=7579eb4e7646743d input=99951d1832fece9e]*/ +/*[clinic end generated code: output=7579eb4e7646743d input=25e989b6d87a3a21]*/ { PyObject *v; diff --git a/Python/clinic/bltinmodule.c.h b/Python/clinic/bltinmodule.c.h index 085b43ced7728b..f75a8d4ac0ccd4 100644 --- a/Python/clinic/bltinmodule.c.h +++ b/Python/clinic/bltinmodule.c.h @@ -395,7 +395,7 @@ builtin_divmod(PyObject *module, PyObject *const *args, Py_ssize_t nargs) } PyDoc_STRVAR(builtin_eval__doc__, -"eval($module, /, source, globals=None, locals=None)\n" +"eval($module, source, /, globals=None, locals=None)\n" "--\n" "\n" "Evaluate the given source in the context of globals and locals.\n" @@ -419,14 +419,14 @@ builtin_eval(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - #define NUM_KEYWORDS 3 + #define NUM_KEYWORDS 2 static struct { PyGC_Head _this_is_not_used; PyObject_VAR_HEAD PyObject *ob_item[NUM_KEYWORDS]; } _kwtuple = { .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) - .ob_item = { &_Py_ID(source), &_Py_ID(globals), &_Py_ID(locals), }, + .ob_item = { &_Py_ID(globals), &_Py_ID(locals), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -435,7 +435,7 @@ builtin_eval(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject # define KWTUPLE NULL #endif // !Py_BUILD_CORE - static const char * const _keywords[] = {"source", "globals", "locals", NULL}; + static const char * const _keywords[] = {"", "globals", "locals", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, .fname = "eval", @@ -471,7 +471,7 @@ builtin_eval(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject } PyDoc_STRVAR(builtin_exec__doc__, -"exec($module, /, source, globals=None, locals=None, *, closure=None)\n" +"exec($module, source, /, globals=None, locals=None, *, closure=None)\n" "--\n" "\n" "Execute the given source in the context of globals and locals.\n" @@ -497,14 +497,14 @@ builtin_exec(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - #define NUM_KEYWORDS 4 + #define NUM_KEYWORDS 3 static struct { PyGC_Head _this_is_not_used; PyObject_VAR_HEAD PyObject *ob_item[NUM_KEYWORDS]; } _kwtuple = { .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) - .ob_item = { &_Py_ID(source), &_Py_ID(globals), &_Py_ID(locals), &_Py_ID(closure), }, + .ob_item = { &_Py_ID(globals), &_Py_ID(locals), &_Py_ID(closure), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -513,7 +513,7 @@ builtin_exec(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject # define KWTUPLE NULL #endif // !Py_BUILD_CORE - static const char * const _keywords[] = {"source", "globals", "locals", "closure", NULL}; + static const char * const _keywords[] = {"", "globals", "locals", "closure", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, .fname = "exec", @@ -1228,4 +1228,4 @@ builtin_issubclass(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=48a6024d50fc008e input=a9049054013a1b77]*/ +/*[clinic end generated code: output=435d3f286a863c49 input=a9049054013a1b77]*/