summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_builtin.py
diff options
context:
space:
mode:
authorlarryhastings <larry@hastings.org>2022-05-06 17:09:35 (GMT)
committerGitHub <noreply@github.com>2022-05-06 17:09:35 (GMT)
commit50210643902d28405a57a9672347f43215dbdb3b (patch)
tree736c0a00668e4781b5db125c557f094b65302f8b /Lib/test/test_builtin.py
parent973a5203c151efb7a86a478140f7b0c9ae70438f (diff)
downloadcpython-50210643902d28405a57a9672347f43215dbdb3b.zip
cpython-50210643902d28405a57a9672347f43215dbdb3b.tar.gz
cpython-50210643902d28405a57a9672347f43215dbdb3b.tar.bz2
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.
Diffstat (limited to 'Lib/test/test_builtin.py')
-rw-r--r--Lib/test/test_builtin.py80
1 files changed, 79 insertions, 1 deletions
diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py
index 2903923..ba7a7e2 100644
--- a/Lib/test/test_builtin.py
+++ b/Lib/test/test_builtin.py
@@ -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])