Dynamic Execution: eval(), exec(), and compile() in Python


Table of Contents

  • Introduction
  • Understanding Dynamic Execution
  • The eval() Function
    • Syntax
    • Examples
    • Security Considerations
  • The exec() Function
    • Syntax
    • Examples
    • Use Cases
  • The compile() Function
    • Syntax
    • Examples
    • How it Integrates with eval() and exec()
  • Practical Scenarios for Dynamic Execution
  • Security Risks and Best Practices
  • Conclusion

Introduction

Python offers several mechanisms for dynamic execution—the ability to execute code dynamically at runtime. This is possible through three powerful built-in functions: eval(), exec(), and compile().

While these tools can greatly enhance flexibility, they can also introduce significant security risks if not used cautiously. In this article, we’ll explore each of these functions in depth, learn how and when to use them, and understand the best practices to follow.


Understanding Dynamic Execution

Dynamic execution refers to the ability to generate and execute code during the program’s runtime. Unlike static code that is written and compiled before running, dynamic code can be created, compiled, and executed while the program is already running.

Dynamic execution can be particularly useful in:

  • Scripting engines
  • Code generation tools
  • Mathematical expression evaluators
  • Interactive interpreters

However, it must be used carefully to avoid critical vulnerabilities like code injection.


The eval() Function

Syntax

eval(expression, globals=None, locals=None)
  • expression: A string containing a single Python expression.
  • globals (optional): Dictionary to specify the global namespace.
  • locals (optional): Dictionary to specify the local namespace.

Examples

Evaluate a simple arithmetic expression:

result = eval('2 + 3 * 5')
print(result) # Output: 17

Using globals and locals:

x = 10
print(eval('x + 5')) # Output: 15

globals_dict = {'x': 7}
print(eval('x + 5', globals_dict)) # Output: 12

Security Considerations

The eval() function is extremely powerful but very dangerous if used with untrusted input. It can execute arbitrary code.

Example of a dangerous input:

user_input = "__import__('os').system('rm -rf /')"
eval(user_input) # This could delete critical files if executed!

Best practice: Avoid using eval() on user-supplied input without strict sanitization or avoid it altogether.


The exec() Function

Syntax

exec(object, globals=None, locals=None)
  • object: A string (or code object) containing valid Python code, which may consist of statements, function definitions, classes, etc.
  • globals (optional): Dictionary for global variables.
  • locals (optional): Dictionary for local variables.

Examples

Executing multiple statements:

code = '''
for i in range(3):
print(i)
'''
exec(code)
# Output:
# 0
# 1
# 2

Defining a function dynamically:

exec('def greet(name): print(f"Hello, {name}!")')
greet('Alice') # Output: Hello, Alice!

Using custom global and local scopes:

globals_dict = {}
locals_dict = {}
exec('x = 5', globals_dict, locals_dict)
print(locals_dict['x']) # Output: 5

Use Cases

  • Dynamic creation of classes and functions
  • Running dynamically generated code blocks
  • Embedded scripting within applications

The compile() Function

Syntax

compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)
  • source: A string or AST object containing Python code.
  • filename: Name of the file from which the code was read (can be a dummy name if generated dynamically).
  • mode: Either 'exec', 'eval', or 'single'.
  • flags, dont_inherit, optimize: Advanced parameters for fine-tuning compilation behavior.

Examples

Compiling and evaluating an expression:

code_obj = compile('2 + 3', '<string>', 'eval')
result = eval(code_obj)
print(result) # Output: 5

Compiling and executing a block:

code_block = """
for i in range(2):
print('Compiled and Executed:', i)
"""
compiled_code = compile(code_block, '<string>', 'exec')
exec(compiled_code)

Creating a function dynamically:

function_code = compile('def square(x): return x * x', '<string>', 'exec')
exec(function_code)
print(square(5)) # Output: 25

How it Integrates with eval() and exec()

  • compile() creates a code object.
  • eval() or exec() can then execute that code object.
  • This two-step process gives you better control and safety.

Practical Scenarios for Dynamic Execution

  • Scripting Engines: Allow users to submit Python scripts to be executed within a controlled environment.
  • Dynamic Configuration: Evaluate mathematical expressions or small scripts stored in configuration files.
  • Custom DSLs (Domain-Specific Languages): Implement mini-languages inside applications.
  • Interactive Consoles: Build REPL (Read-Eval-Print Loop) systems for debugging or educational purposes.

Example of a mini calculator:

def simple_calculator(expression):
try:
return eval(expression)
except Exception as e:
return f"Error: {e}"

print(simple_calculator('10 * (5 + 3)')) # Output: 80

Important: Always validate or sandbox the input!


Security Risks and Best Practices

RiskPrevention
Arbitrary Code ExecutionNever use eval(), exec(), or compile() with untrusted input.
Resource Exhaustion AttacksSet execution timeouts if using dynamic code in servers or services.
Namespace PollutionUse restricted globals and locals dictionaries when executing dynamic code.
Hidden VulnerabilitiesAudit dynamic code paths carefully and avoid if simpler alternatives exist.

If you must dynamically execute code:

  • Validate and sanitize all inputs.
  • Consider alternatives like literal_eval from ast module for safe evaluation of expressions.
  • Use a sandboxed environment or process isolation if executing untrusted code.

Example of safer evaluation:

import ast

expr = "2 + 3 * 4"
safe_expr = ast.literal_eval(expr)
print(safe_expr) # Raises an error because only literals are allowed.

Conclusion

Python’s dynamic execution capabilities via eval(), exec(), and compile() are powerful tools that open up a wide array of possibilities, from building interpreters to creating highly flexible systems.

However, with great power comes great responsibility. Misusing these functions can introduce severe vulnerabilities into your application. Always prefer safer alternatives and carefully vet the necessity of dynamic execution in your projects.

A deep understanding of these tools allows you to leverage Python’s full dynamic potential while maintaining safe, maintainable, and professional code.