diff -r 6e7a083f893f Doc/reference/simple_stmts.rst --- a/Doc/reference/simple_stmts.rst Sat Nov 24 18:25:25 2012 +0100 +++ b/Doc/reference/simple_stmts.rst Sun Nov 25 11:06:15 2012 +0000 @@ -978,15 +978,15 @@ exec_stmt: "exec" `or_expr` ["in" `expression` ["," `expression`]] This statement supports dynamic execution of Python code. The first expression -should evaluate to either a string, an open file object, or a code object. If +should evaluate to either a string, an open file object, a code object, or a tuple. If it is a string, the string is parsed as a suite of Python statements which is then executed (unless a syntax error occurs). [#]_ If it is an open file, the file is parsed until EOF and executed. If it is a code object, it is simply -executed. In all cases, the code that's executed is expected to be valid as -file input (see section :ref:`file-input`). Be aware that the -:keyword:`return` and :keyword:`yield` statements may not be used outside of -function definitions even within the context of code passed to the -:keyword:`exec` statement. +executed. For the interpretation of a tuple, see below. In all cases, the +code that's executed is expected to be valid as file input (see section +:ref:`file-input`). Be aware that the :keyword:`return` and :keyword:`yield` +statements may not be used outside of function definitions even within the +context of code passed to the :keyword:`exec` statement. In all cases, if the optional parts are omitted, the code is executed in the current scope. If only the first expression after :keyword:`in` is specified, @@ -997,6 +997,12 @@ two separate objects are given as *globals* and *locals*, the code will be executed as if it were embedded in a class definition. +For backwards compatibility, the first expression may also be a tuple of length +2 or 3. In this case, the optional parts must be omitted. The form +``exec(expr, globals)`` is equivalent to ``exec expr in globals``, +while the form ``exec(expr, globals, locals)`` is equivalent to ``exec +expr in globals, locals``. + .. versionchanged:: 2.4 Formerly, *locals* was required to be a dictionary. diff -r 6e7a083f893f Lib/test/test_compile.py --- a/Lib/test/test_compile.py Sat Nov 24 18:25:25 2012 +0100 +++ b/Lib/test/test_compile.py Sun Nov 25 11:06:15 2012 +0000 @@ -61,6 +61,34 @@ except SyntaxError: pass + def test_exec_functional_style(self): + # Exec'ing a tuple of length 2 works. + g = {'b': 2} + exec("a = b + 1", g) + self.assertEqual(g['a'], 3) + + # As does exec'ing a tuple of length 3. + l = {'b': 3} + g = {'b': 5, 'c': 7} + exec("a = b + c", g, l) + self.assertNotIn('a', g) + self.assertEqual(l['a'], 10) + + # Tuples not of length 2 or 3 are invalid. + with self.assertRaises(TypeError): + exec("a = b + 1",) + + with self.assertRaises(TypeError): + exec("a = b + 1", {}, {}, {}) + + # Can't mix and match the two calling forms. + g = {'a': 3, 'b': 4} + l = {} + with self.assertRaises(TypeError): + exec("a = b + 1", g) in g + with self.assertRaises(TypeError): + exec("a = b + 1", g, l) in g, l + def test_exec_with_general_mapping_for_locals(self): class M: