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:
- 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.
- Both the eval() and exec() take two optional arguments: globals and locals.
These functions differ in the following aspects:
- eval() returns the result of the expression, while exec() does not.
- 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 '
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, '
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!