I found several options for doing this, before Antti posted his answer that Python 2 supports the Python 3 exec function syntax.
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.
If you don't want to use that for some reason, here are all the other options I found.
Import Stubs
You can declare two different import stubs and import whichever one works with the current interpreter. This is based on what I saw in the PyDev source code.
Here's what you put in the main module:
try:
from exec_python2 import exec_code #@UnusedImport
except:
from exec_python3 import exec_code #@Reimport
Here's what you put in exec_python2.py:
def exec_code(source, global_vars, local_vars):
exec source in global_vars, local_vars
Here's what you put in exec_python3.py:
def exec_code(source, global_vars, local_vars):
exec(source, global_vars, local_vars)
Exec in Eval
Ned Batchelder posted a technique that wraps the exec statement in a call to eval so it won't cause a syntax error in Python 3. It's clever, but not clear.
# Exec is a statement in Py2, a function in Py3
if sys.hexversion > 0x03000000:
def exec_function(source, filename, global_map):
"""A wrapper around exec()."""
exec(compile(source, filename, "exec"), global_map)
else:
# OK, this is pretty gross. In Py2, exec was a statement, but that will
# be a syntax error if we try to put it in a Py3 file, even if it isn't
# executed. So hide it inside an evaluated string literal instead.
eval(compile("""\
def exec_function(source, filename, global_map):
exec compile(source, filename, "exec") in global_map
""",
"<exec_function>", "exec"
))
Six package
The six package is a compatibility library for writing code that will run under both Python 2 and Python 3. It has an exec_() function that translates to both versions. I haven't tried it.