mirror of
https://github.com/python/cpython.git
synced 2025-09-27 02:39:58 +00:00
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
5 changed files with 171 additions and 21 deletions
|
@ -552,7 +552,7 @@ are always available. They are listed here in alphabetical order.
|
||||||
|
|
||||||
.. index:: builtin: exec
|
.. 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
|
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
|
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 are available to the executed code by inserting your own
|
||||||
``__builtins__`` dictionary into *globals* before passing it to :func:`exec`.
|
``__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
|
.. audit-event:: exec code_object exec
|
||||||
|
|
||||||
Raises an :ref:`auditing event <auditing>` ``exec`` with the code object
|
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
|
Pass an explicit *locals* dictionary if you need to see effects of the
|
||||||
code on *locals* after function :func:`exec` returns.
|
code on *locals* after function :func:`exec` returns.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.11
|
||||||
|
Added the *closure* parameter.
|
||||||
|
|
||||||
|
|
||||||
.. function:: filter(function, iterable)
|
.. function:: filter(function, iterable)
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ from functools import partial
|
||||||
from inspect import CO_COROUTINE
|
from inspect import CO_COROUTINE
|
||||||
from itertools import product
|
from itertools import product
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
from types import AsyncGeneratorType, FunctionType
|
from types import AsyncGeneratorType, FunctionType, CellType
|
||||||
from operator import neg
|
from operator import neg
|
||||||
from test import support
|
from test import support
|
||||||
from test.support import (swap_attr, maybe_get_event_loop_policy)
|
from test.support import (swap_attr, maybe_get_event_loop_policy)
|
||||||
|
@ -772,6 +772,84 @@ class BuiltinTest(unittest.TestCase):
|
||||||
finally:
|
finally:
|
||||||
sys.stdout = savestdout
|
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):
|
def test_filter(self):
|
||||||
self.assertEqual(list(filter(lambda c: 'a' <= c <= 'z', 'Hello World')), list('elloorld'))
|
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])
|
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
|
globals: object = None
|
||||||
locals: object = None
|
locals: object = None
|
||||||
/
|
/
|
||||||
|
*
|
||||||
|
closure: object(c_default="NULL") = None
|
||||||
|
|
||||||
Execute the given source in the context of globals and locals.
|
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,
|
The globals must be a dictionary and locals can be any mapping,
|
||||||
defaulting to the current globals and locals.
|
defaulting to the current globals and locals.
|
||||||
If only globals is given, locals defaults to it.
|
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]*/
|
[clinic start generated code]*/
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
|
builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
|
||||||
PyObject *locals)
|
PyObject *locals, PyObject *closure)
|
||||||
/*[clinic end generated code: output=3c90efc6ab68ef5d input=01ca3e1c01692829]*/
|
/*[clinic end generated code: output=7579eb4e7646743d input=f13a7e2b503d1d9a]*/
|
||||||
{
|
{
|
||||||
PyObject *v;
|
PyObject *v;
|
||||||
|
|
||||||
|
@ -1029,20 +1033,60 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (closure == Py_None) {
|
||||||
|
closure = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (PyCode_Check(source)) {
|
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) {
|
if (PySys_Audit("exec", "O", source) < 0) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PyCode_GetNumFree((PyCodeObject *)source) > 0) {
|
if (!closure) {
|
||||||
PyErr_SetString(PyExc_TypeError,
|
|
||||||
"code object passed to exec() may not "
|
|
||||||
"contain free variables");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
v = PyEval_EvalCode(source, globals, locals);
|
v = PyEval_EvalCode(source, globals, locals);
|
||||||
|
} else {
|
||||||
|
v = PyEval_EvalCodeEx(source, globals, locals,
|
||||||
|
NULL, 0,
|
||||||
|
NULL, 0,
|
||||||
|
NULL, 0,
|
||||||
|
NULL,
|
||||||
|
closure);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
if (closure != NULL) {
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"closure can only be used when source is a code object");
|
||||||
|
}
|
||||||
PyObject *source_copy;
|
PyObject *source_copy;
|
||||||
const char *str;
|
const char *str;
|
||||||
PyCompilerFlags cf = _PyCompilerFlags_INIT;
|
PyCompilerFlags cf = _PyCompilerFlags_INIT;
|
||||||
|
|
37
Python/clinic/bltinmodule.c.h
generated
37
Python/clinic/bltinmodule.c.h
generated
|
@ -408,7 +408,7 @@ exit:
|
||||||
}
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(builtin_exec__doc__,
|
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"
|
||||||
"\n"
|
"\n"
|
||||||
"Execute the given source in the context of globals and locals.\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"
|
"or a code object as returned by compile().\n"
|
||||||
"The globals must be a dictionary and locals can be any mapping,\n"
|
"The globals must be a dictionary and locals can be any mapping,\n"
|
||||||
"defaulting to the current globals and locals.\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 \
|
#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 *
|
static PyObject *
|
||||||
builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
|
builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
|
||||||
PyObject *locals);
|
PyObject *locals, PyObject *closure);
|
||||||
|
|
||||||
static PyObject *
|
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;
|
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 *source;
|
||||||
PyObject *globals = Py_None;
|
PyObject *globals = Py_None;
|
||||||
PyObject *locals = 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;
|
goto exit;
|
||||||
}
|
}
|
||||||
source = args[0];
|
source = args[0];
|
||||||
if (nargs < 2) {
|
if (nargs < 2) {
|
||||||
goto skip_optional;
|
goto skip_optional_posonly;
|
||||||
}
|
}
|
||||||
|
noptargs--;
|
||||||
globals = args[1];
|
globals = args[1];
|
||||||
if (nargs < 3) {
|
if (nargs < 3) {
|
||||||
goto skip_optional;
|
goto skip_optional_posonly;
|
||||||
}
|
}
|
||||||
|
noptargs--;
|
||||||
locals = args[2];
|
locals = args[2];
|
||||||
skip_optional:
|
skip_optional_posonly:
|
||||||
return_value = builtin_exec_impl(module, source, globals, locals);
|
if (!noptargs) {
|
||||||
|
goto skip_optional_kwonly;
|
||||||
|
}
|
||||||
|
closure = args[3];
|
||||||
|
skip_optional_kwonly:
|
||||||
|
return_value = builtin_exec_impl(module, source, globals, locals, closure);
|
||||||
|
|
||||||
exit:
|
exit:
|
||||||
return return_value;
|
return return_value;
|
||||||
|
@ -1030,4 +1045,4 @@ builtin_issubclass(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
|
||||||
exit:
|
exit:
|
||||||
return return_value;
|
return return_value;
|
||||||
}
|
}
|
||||||
/*[clinic end generated code: output=6a2b78ef82bc5155 input=a9049054013a1b77]*/
|
/*[clinic end generated code: output=a2c5c53e8aead7c3 input=a9049054013a1b77]*/
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue