]> granicus.if.org Git - python/commitdiff
Issue #16339: Document and test exec(stmt, globals, locals) form in Python 2.7.
authorMark Dickinson <mdickinson@enthought.com>
Sun, 25 Nov 2012 13:25:57 +0000 (13:25 +0000)
committerMark Dickinson <mdickinson@enthought.com>
Sun, 25 Nov 2012 13:25:57 +0000 (13:25 +0000)
Doc/reference/simple_stmts.rst
Lib/test/test_compile.py

index a9766a4b95d40d8e7385db2ea9909c0c942f8e7d..843ce1727b2e6344067bb1e30e66a7573b6c6ae5 100644 (file)
@@ -978,18 +978,18 @@ The :keyword:`exec` statement
    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
-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.
+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.  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,
+current scope.  If only the first expression after ``in`` is specified,
 it should be a dictionary, which will be used for both the global and the local
 variables.  If two expressions are given, they are used for the global and local
 variables, respectively. If provided, *locals* can be any mapping object.
@@ -997,6 +997,13 @@ Remember that at module level, globals and locals are the same dictionary. If
 two separate objects are given as *globals* and *locals*, the code will be
 executed as if it were embedded in a class definition.
 
+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``.  The tuple form of ``exec``
+provides compatibility with Python 3, where ``exec`` is a function rather than
+a statement.
+
 .. versionchanged:: 2.4
    Formerly, *locals* was required to be a dictionary.
 
index 22d170823150eb62094889fc5ad6ac36c1d3bc41..4bcb43840853aa00dd560d642645c16d21f04948 100644 (file)
@@ -61,6 +61,34 @@ class TestSpecifics(unittest.TestCase):
         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: