mirror of https://github.com/python/cpython
gh-92203: Add closure support to exec(). (#92204)
Add a closure keyword-only parameter to exec(). It can only be specified when exec-ing a code object that uses free variables. When specified, it must be a tuple, with exactly the number of cell variables referenced by the code object. closure has a default value of None, and it must be None if the code object doesn't refer to any free variables.
This commit is contained in:
parent
973a5203c1
commit
5021064390
|
@ -552,7 +552,7 @@ are always available. They are listed here in alphabetical order.
|
|||
|
||||
.. index:: builtin: exec
|
||||
|
||||
.. function:: exec(object[, globals[, locals]])
|
||||
.. function:: exec(object[, globals[, locals]], *, 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
|
||||
|
@ -581,6 +581,11 @@ are always available. They are listed here in alphabetical order.
|
|||
builtins are available to the executed code by inserting your own
|
||||
``__builtins__`` dictionary into *globals* before passing it to :func:`exec`.
|
||||
|
||||
The *closure* argument specifies a closure--a tuple of cellvars.
|
||||
It's only valid when the *object* is a code object containing free variables.
|
||||
The length of the tuple must exactly match the number of free variables
|
||||
referenced by the code object.
|
||||
|
||||
.. audit-event:: exec code_object exec
|
||||
|
||||
Raises an :ref:`auditing event <auditing>` ``exec`` with the code object
|
||||
|
@ -599,6 +604,9 @@ are always available. They are listed here in alphabetical order.
|
|||
Pass an explicit *locals* dictionary if you need to see effects of the
|
||||
code on *locals* after function :func:`exec` returns.
|
||||
|
||||
.. versionchanged:: 3.11
|
||||
Added the *closure* parameter.
|
||||
|
||||
|
||||
.. function:: filter(function, iterable)
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ from functools import partial
|
|||
from inspect import CO_COROUTINE
|
||||
from itertools import product
|
||||
from textwrap import dedent
|
||||
from types import AsyncGeneratorType, FunctionType
|
||||
from types import AsyncGeneratorType, FunctionType, CellType
|
||||
from operator import neg
|
||||
from test import support
|
||||
from test.support import (swap_attr, maybe_get_event_loop_policy)
|
||||
|
@ -772,6 +772,84 @@ class BuiltinTest(unittest.TestCase):
|
|||
finally:
|
||||
sys.stdout = savestdout
|
||||
|
||||
def test_exec_closure(self):
|
||||
def function_without_closures():
|
||||
return 3 * 5
|
||||
|
||||
result = 0
|
||||
def make_closure_functions():
|
||||
a = 2
|
||||
b = 3
|
||||
c = 5
|
||||
def three_freevars():
|
||||
nonlocal result
|
||||
nonlocal a
|
||||
nonlocal b
|
||||
result = a*b
|
||||
def four_freevars():
|
||||
nonlocal result
|
||||
nonlocal a
|
||||
nonlocal b
|
||||
nonlocal c
|
||||
result = a*b*c
|
||||
return three_freevars, four_freevars
|
||||
three_freevars, four_freevars = make_closure_functions()
|
||||
|
||||
# "smoke" test
|
||||
result = 0
|
||||
exec(three_freevars.__code__,
|
||||
three_freevars.__globals__,
|
||||
closure=three_freevars.__closure__)
|
||||
self.assertEqual(result, 6)
|
||||
|
||||
# should also work with a manually created closure
|
||||
result = 0
|
||||
my_closure = (CellType(35), CellType(72), three_freevars.__closure__[2])
|
||||
exec(three_freevars.__code__,
|
||||
three_freevars.__globals__,
|
||||
closure=my_closure)
|
||||
self.assertEqual(result, 2520)
|
||||
|
||||
# should fail: closure isn't allowed
|
||||
# for functions without free vars
|
||||
self.assertRaises(TypeError,
|
||||
exec,
|
||||
function_without_closures.__code__,
|
||||
function_without_closures.__globals__,
|
||||
closure=my_closure)
|
||||
|
||||
# should fail: closure required but wasn't specified
|
||||
self.assertRaises(TypeError,
|
||||
exec,
|
||||
three_freevars.__code__,
|
||||
three_freevars.__globals__,
|
||||
closure=None)
|
||||
|
||||
# should fail: closure of wrong length
|
||||
self.assertRaises(TypeError,
|
||||
exec,
|
||||
three_freevars.__code__,
|
||||
three_freevars.__globals__,
|
||||
closure=four_freevars.__closure__)
|
||||
|
||||
# should fail: closure using a list instead of a tuple
|
||||
my_closure = list(my_closure)
|
||||
self.assertRaises(TypeError,
|
||||
exec,
|
||||
three_freevars.__code__,
|
||||
three_freevars.__globals__,
|
||||
closure=my_closure)
|
||||
|
||||
# should fail: closure tuple with one non-cell-var
|
||||
my_closure[0] = int
|
||||
my_closure = tuple(my_closure)
|
||||
self.assertRaises(TypeError,
|
||||
exec,
|
||||
three_freevars.__code__,
|
||||
three_freevars.__globals__,
|
||||
closure=my_closure)
|
||||
|
||||
|
||||
def test_filter(self):
|
||||
self.assertEqual(list(filter(lambda c: 'a' <= c <= 'z', 'Hello World')), list('elloorld'))
|
||||
self.assertEqual(list(filter(None, [1, 'hello', [], [3], '', None, 9, 0])), [1, 'hello', [3], 9])
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
Add a closure keyword-only parameter to exec(). It can only be specified
|
||||
when exec-ing a code object that uses free variables. When specified, it
|
||||
must be a tuple, with exactly the number of cell variables referenced by the
|
||||
code object. closure has a default value of None, and it must be None if the
|
||||
code object doesn't refer to any free variables.
|
|
@ -977,6 +977,8 @@ exec as builtin_exec
|
|||
globals: object = None
|
||||
locals: object = None
|
||||
/
|
||||
*
|
||||
closure: object(c_default="NULL") = None
|
||||
|
||||
Execute the given source in the context of globals and locals.
|
||||
|
||||
|
@ -985,12 +987,14 @@ or a code object as returned by compile().
|
|||
The globals must be a dictionary and locals can be any mapping,
|
||||
defaulting to the current globals and locals.
|
||||
If only globals is given, locals defaults to it.
|
||||
The closure must be a tuple of cellvars, and can only be used
|
||||
when source is a code object requiring exactly that many cellvars.
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
|
||||
PyObject *locals)
|
||||
/*[clinic end generated code: output=3c90efc6ab68ef5d input=01ca3e1c01692829]*/
|
||||
PyObject *locals, PyObject *closure)
|
||||
/*[clinic end generated code: output=7579eb4e7646743d input=f13a7e2b503d1d9a]*/
|
||||
{
|
||||
PyObject *v;
|
||||
|
||||
|
@ -1029,20 +1033,60 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
if (closure == Py_None) {
|
||||
closure = NULL;
|
||||
}
|
||||
|
||||
if (PyCode_Check(source)) {
|
||||
Py_ssize_t num_free = PyCode_GetNumFree((PyCodeObject *)source);
|
||||
if (num_free == 0) {
|
||||
if (closure) {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"cannot use a closure with this code object");
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
int closure_is_ok =
|
||||
closure
|
||||
&& PyTuple_CheckExact(closure)
|
||||
&& (PyTuple_GET_SIZE(closure) == num_free);
|
||||
if (closure_is_ok) {
|
||||
for (Py_ssize_t i = 0; i < num_free; i++) {
|
||||
PyObject *cell = PyTuple_GET_ITEM(closure, i);
|
||||
if (!PyCell_Check(cell)) {
|
||||
closure_is_ok = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!closure_is_ok) {
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"code object requires a closure of exactly length %zd",
|
||||
num_free);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (PySys_Audit("exec", "O", source) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (PyCode_GetNumFree((PyCodeObject *)source) > 0) {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"code object passed to exec() may not "
|
||||
"contain free variables");
|
||||
return NULL;
|
||||
if (!closure) {
|
||||
v = PyEval_EvalCode(source, globals, locals);
|
||||
} else {
|
||||
v = PyEval_EvalCodeEx(source, globals, locals,
|
||||
NULL, 0,
|
||||
NULL, 0,
|
||||
NULL, 0,
|
||||
NULL,
|
||||
closure);
|
||||
}
|
||||
v = PyEval_EvalCode(source, globals, locals);
|
||||
}
|
||||
else {
|
||||
if (closure != NULL) {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"closure can only be used when source is a code object");
|
||||
}
|
||||
PyObject *source_copy;
|
||||
const char *str;
|
||||
PyCompilerFlags cf = _PyCompilerFlags_INIT;
|
||||
|
|
|
@ -408,7 +408,7 @@ exit:
|
|||
}
|
||||
|
||||
PyDoc_STRVAR(builtin_exec__doc__,
|
||||
"exec($module, source, globals=None, locals=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"
|
||||
|
@ -417,37 +417,52 @@ PyDoc_STRVAR(builtin_exec__doc__,
|
|||
"or a code object as returned by compile().\n"
|
||||
"The globals must be a dictionary and locals can be any mapping,\n"
|
||||
"defaulting to the current globals and locals.\n"
|
||||
"If only globals is given, locals defaults to it.");
|
||||
"If only globals is given, locals defaults to it.\n"
|
||||
"The closure must be a tuple of cellvars, and can only be used\n"
|
||||
"when source is a code object requiring exactly that many cellvars.");
|
||||
|
||||
#define BUILTIN_EXEC_METHODDEF \
|
||||
{"exec", _PyCFunction_CAST(builtin_exec), METH_FASTCALL, builtin_exec__doc__},
|
||||
{"exec", _PyCFunction_CAST(builtin_exec), METH_FASTCALL|METH_KEYWORDS, builtin_exec__doc__},
|
||||
|
||||
static PyObject *
|
||||
builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
|
||||
PyObject *locals);
|
||||
PyObject *locals, PyObject *closure);
|
||||
|
||||
static PyObject *
|
||||
builtin_exec(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
|
||||
builtin_exec(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
|
||||
{
|
||||
PyObject *return_value = NULL;
|
||||
static const char * const _keywords[] = {"", "", "", "closure", NULL};
|
||||
static _PyArg_Parser _parser = {NULL, _keywords, "exec", 0};
|
||||
PyObject *argsbuf[4];
|
||||
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
|
||||
PyObject *source;
|
||||
PyObject *globals = Py_None;
|
||||
PyObject *locals = Py_None;
|
||||
PyObject *closure = NULL;
|
||||
|
||||
if (!_PyArg_CheckPositional("exec", 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;
|
||||
goto skip_optional_posonly;
|
||||
}
|
||||
noptargs--;
|
||||
globals = args[1];
|
||||
if (nargs < 3) {
|
||||
goto skip_optional;
|
||||
goto skip_optional_posonly;
|
||||
}
|
||||
noptargs--;
|
||||
locals = args[2];
|
||||
skip_optional:
|
||||
return_value = builtin_exec_impl(module, source, globals, locals);
|
||||
skip_optional_posonly:
|
||||
if (!noptargs) {
|
||||
goto skip_optional_kwonly;
|
||||
}
|
||||
closure = args[3];
|
||||
skip_optional_kwonly:
|
||||
return_value = builtin_exec_impl(module, source, globals, locals, closure);
|
||||
|
||||
exit:
|
||||
return return_value;
|
||||
|
@ -1030,4 +1045,4 @@ builtin_issubclass(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
|
|||
exit:
|
||||
return return_value;
|
||||
}
|
||||
/*[clinic end generated code: output=6a2b78ef82bc5155 input=a9049054013a1b77]*/
|
||||
/*[clinic end generated code: output=a2c5c53e8aead7c3 input=a9049054013a1b77]*/
|
||||
|
|
Loading…
Reference in New Issue