Python @ DjangoSpin

Python: Executing Python statements dynamically with compile(), exec() & eval()

Buffer this pageShare on FacebookPrint this pageTweet about this on TwitterShare on Google+Share on LinkedInShare on StumbleUpon
Reading Time: 2 minutes

Executing statements dynamically with compile(), exec() & eval() in Python

Executing statements dynamically with compile(), exec() & eval() in Python

Python offers two builtin functions to execute pieces of code put together as a string: eval() & exec(). These functions can be used to execute command inputs from a user, like in a custom interpreter. Optionally, these pieces of code which are in the form of a string, can be fed to the builtin compile() function first, to create a code object (Python bytecode), which can then be handed over to eval() & exec() for execution.

Let's set the pretext to our discussion today.

>>> help(eval)
Help on built-in function eval in module builtins:

eval(...)
    eval(source[, globals[, locals]]) -> value
    
    Evaluate the source in the context of globals and locals.
    The source may be a string representing a Python expression
    or a code object as returned by compile().
    The globals must be a dictionary and locals can be any mapping,
    defaulting to the current globals and locals.
    If only globals is given, locals defaults to it.
	
>>> help(exec)
Help on built-in function exec in module builtins:

exec(...)
    exec(object[, globals[, locals]])
    
    Read and execute code from an object, which can be a string or a code
    object.
    The globals and locals are dictionaries, defaulting to the current
    globals and locals.  If only globals is given, locals defaults to it.
	
>>> help(compile)
Help on built-in function compile in module builtins:

compile(...)
    compile(source, filename, mode[, flags[, dont_inherit]]) -> code object
    
    Compile the source (a Python module, statement or expression)
    into a code object that can be executed by exec() or eval().
    The filename will be used for run-time error messages.
    The mode must be 'exec' to compile a module, 'single' to compile a
    single (interactive) statement, or 'eval' to compile an expression.
    The flags argument, if present, controls which future statements influence
    the compilation of the code.
    The dont_inherit argument, if non-zero, stops the compilation inheriting
    the effects of any future statements in effect in the code calling
    compile; if absent or zero these statements do influence the compilation,
    in addition to any features explicitly specified.

So, the builtin functions eval() and exec() are both used to execute Python statements dynamically. These functions have 2 similarities and 2 differences. Similarities:

  1. Both the eval() and exec() evaluate Python statements, which can be in the form of a string, or a code object as returned by the compile() function.
  2. Both the eval() and exec() take two optional arguments: globals and locals.

These functions differ in the following aspects:

  1. eval() returns the result of the expression, while exec() does not.
  2. eval() only evaluates a single expression (anything on the right hand side of an assignment operation), whereas the exec() can take code blocks having loops, try/except, def clauses.

Let's take a look at basic usage of these functions:

>>> a = 10
>>> eval('a + 5')				
15
>>> a
10
>>> exec('a + 10')				
>>> a
10
>>> exec('a = 20')
>>> a
20

Expand the following code snippet for more examples.

# eval() only evaluates an expression and returns the result. An expression is any combination of operators and operands that you can write on the right hand side of an assignment operation. 
>>> a = 10
>>> eval('a + 5')				
15
>>> eval('a = 20')									# does not evaluate anything other than an expression
Traceback (most recent call last):
    eval('a = 20')
  File "<string>", line 1
    a = 20
      ^
SyntaxError: invalid syntax


>>> eval( 'def anyFunction(): print(50)' )			# does not evaluate anything other than an expression
Traceback (most recent call last):
    eval( 'def anyFunction(): print(50)' )
  File "<string>", line 1
    def anyFunction(): print(50)
      ^
SyntaxError: invalid syntax

>>> eval('"Hi"')									# has a return value
'Hi'


>>> eval('if 1: print("Hi")')						# does not evaluate anything other than an expression
Traceback (most recent call last):
    eval('if 1: print("Hi")')
  File "<string>", line 1
    if 1: print("Hi")
     ^
SyntaxError: invalid syntax



# exec() supports execution of code-blocks, including statements such as assignment. It does not return any value.
>>> exec('a = 20')
>>> a
20

>>> exec( 'def anyFunction(): print(50)' )
>>> anyFunction()
50

>>> exec('print(1) \nprint(2)')
1
2

>>> exec('if 1: print("Hi")')
Hi

>>> exec('"Hi"')									# No output; no return value

In addition to the string form, the statements being passed to exec() & eval() can be in the form of a code object. The compile() function compiles the provided module/statement/expression, and creates a code object (contains Python bytecode) which can be passed to exec() & eval(). This is particularly useful when the same piece of code is being evaluated repeatedly.

>>> help(compile)
Help on built-in function compile in module builtins:

compile(...)
    compile(source, filename, mode[, flags[, dont_inherit]]) -> code object
    
    Compile the source (a Python module, statement or expression)
    into a code object that can be executed by exec() or eval().
    The filename will be used for run-time error messages.
    The mode must be 'exec' to compile a module, 'single' to compile a
    single (interactive) statement, or 'eval' to compile an expression.
    The flags argument, if present, controls which future statements influence
    the compilation of the code.
    The dont_inherit argument, if non-zero, stops the compilation inheriting
    the effects of any future statements in effect in the code calling
    compile; if absent or zero these statements do influence the compilation,
    in addition to any features explicitly specified.

Let's talk about the positional arguments.

  • source: module/statement/expression.
  • filename: used for run-time error messages.
  • mode: 'exec'/'single'/'eval'.

Getting the filename argument out of the way of the discussion, the filename argument should give the file from which the code was read. The string entered in this argument can be retrieved using the co_filename attribute of the resultant code object. In case the source is not a part of any file, it is common practice to pass some recognizable value (such as ''). It can also be left as an empty string.

The source & mode arguments are inter-dependent, the following examples will demonstrate this.


# compile(source, '<string>', 'eval') returns the code object which would have been executed if you had executed eval(source).
# source can only be a single expression in this mode.
>>> a = 5
>>> evalCodeObject = compile('a + 10', '<string>', 'eval')
>>> evaluatedValueOfa = eval(evalCodeObject)
>>> evaluatedValueOfa
15


# compile(source, '<string>', 'exec') returns the code object which would have been executed if you had executed exec(source).
# source can be code blocks having loops, try/except, def clauses.
>>> execCodeObject = compile('a = 8; a = a + 10; print(a)', '<string>', 'exec')
>>> executeCodeBlock = exec(execCodeObject)
18


# compile(source, '<string>', 'single') offers a limited functionality of the 'exec' mode, by accepting a single statement or multiple statements separated by semi-colon. It is capable of executing a loop, an if-elif-else construct, a try-except-else-finally construct, a function with semi-colon delimited statements.

>>> singleCodeObject = compile('a = 50; print(a + 4); print(a + 10)', '<string>', 'single')			# multiple statements delimited by ;
>>> executeSingleCodeObject = exec(singleCodeObject)
54
60


>>> codeBlock = '''																					# an if-else construct 
if True:
	print("TRUE!")
else:
	pass
'''
>>> compiledCodeBlock = compile(codeBlock, '<string>', 'single')
>>> exec(compiledCodeBlock)
TRUE!



>>> codeBlock = '''																					# INVALID: two constructs
if True:
	print("TRUE!")
else:
	pass

if True:
	print("TRUE AGAIN!")
else:
	pass
'''
>>> compiledCodeBlock = compile(codeBlock, '<string>', 'single')
Traceback (most recent call last):
    compiledCodeBlock = compile(codeBlock, '<string>', 'single')
  File "<string>", line 7																			# referring to the second construct
    if True:
     ^
SyntaxError: invalid syntax



>>> codeBlock = '''																					# a function definition with semi-colon delimited statements
def functionOne(): print(6); print(12)
'''
>>> compiledCodeBlock = compile(codeBlock, '<string>', 'single')
>>> exec(compiledCodeBlock)
>>> functionOne()
6
12

>>> codeBlock = '''																					# INVALID: two constructs
def functionOne(): print(6); print(12)
def functionTwo(): print(18)
'''
>>> compiledCodeBlock = compile(codeBlock, '<string>', 'single')
Traceback (most recent call last):
    compiledCodeBlock = compile(codeBlock, '<string>', 'single')
  File "<string>", line 3
    def functionTwo(): print(18)
      ^
SyntaxError: invalid syntax

Now that you know about the compile() method and its modes, I believe it is the right time to tell you that the string you pass to exec() & eval() functions becomes a call to compile(source, '', mode) with the respective mode ('exec'/'eval'), which returns the code object (containing Python bytecode), which is finally executed.

Also, it is possible to have the eval() process several statements if it is in the form of a code object.

>>> codeBlock = '''
def functionThree(): print(6); print(12)
def functionFour(): print(18)
'''
>>> compiledCodeBlock = compile(codeBlock, '<string>', 'exec')
>>> eval(compiledCodeBlock)
>>> functionThree()
6
12
SyntaxError: invalid syntax

That's it for this one. I hope that this article helped you wrap your head around the exec(), compile() & eval() functions. Till next time!


Further Reading


See also:

Buffer this pageShare on FacebookPrint this pageTweet about this on TwitterShare on Google+Share on LinkedInShare on StumbleUpon

Leave a Reply