diff options
author | larryhastings <larry@hastings.org> | 2022-05-06 17:09:35 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-05-06 17:09:35 (GMT) |
commit | 50210643902d28405a57a9672347f43215dbdb3b (patch) | |
tree | 736c0a00668e4781b5db125c557f094b65302f8b /Lib/test/test_builtin.py | |
parent | 973a5203c151efb7a86a478140f7b0c9ae70438f (diff) | |
download | cpython-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.py | 80 |
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]) |