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:
larryhastings 2022-05-06 10:09:35 -07:00 committed by GitHub
parent 973a5203c1
commit 5021064390
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 171 additions and 21 deletions

View file

@ -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])