As an expert Python developer, I often get asked about using the eval() and exec() builtin functions for executing dynamic Python code. In this comprehensive guide, we‘ll dive deep into proper usage, security considerations, real-world examples, and best practices when working with eval and exec in Python.
Key Capabilities of Eval and Exec
The eval() and exec() functions in Python enable developers to evaluate and execute string representations of Python code from within another program.
As per PEP 234, the key capabilities provided by eval() and exec() include:
- Executing Python code from a string or other code object at runtime
- Dynamically generating and running Python code
- Evaluating Python expressions provided as string input
- Implementing embeddable command interpreters and debugging interfaces
- Loading modules, extensions, and plugins at runtime
- Providing access to Python‘s runtime environment through restricted namespaces
These dynamic execution functions are very versatile for building powerful Python programs and tools. However, they also introduce risks which we‘ll examine later on.
Eval() Usage and Examples
The eval() function evaluates a Python expression passed to it as a string and returns the result. For example:
result = eval(‘1 + 2‘)
print(result) # Prints 3
eval() has full access to globals() and locals() namespaces to reference variables and call functions defined there. For example:
import math
x = 5
eval_result = eval(‘math.sqrt(x)‘)
print(eval_result) # Prints 2.23606797749979
Here are some common use cases where eval() is handy:
1. Evaluating Mathematical and Dynamic Expressions
eval() allows evaluating math formulas and expressions input by users:
user_input = input("Enter a math formula: ")
result = eval(user_input)
print("Result:", result)
It can parse and evaluate even complex numeric expressions and formulae.
2. Calling Python Functions/Methods Dynamically
You can use strings to call functions defined in the current namespace:
func_call_str = ‘print("Hello world")‘
eval(func_call_str) # Prints Hello world
This technique is commonly used in web frameworks to invoke view functions based on routing rules.
3. Dynamic Object Instantiation/Attribute Access
eval() enables creating objects and accessing attributes dynamically:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p = eval(‘Person("John", 32)‘)
print(p.name) # Prints John
attr_str = ‘p.age‘
print(eval(attr_str)) # Prints 32
This approach can be used to implement dynamic ORM libraries and serialization.
Exec() Usage and Examples
While eval() is limited to evaluating Python expressions, exec() can execute compound Python statements provided in string form:
exec_str = """
x = 10
print(‘Value set:‘, x)
"""
exec(exec_str) # Prints Value set: 10
Unlike eval(), exec() always returns None. The executed code runs in the current namespaces, so globals/locals get modified.
Some good use cases of exec() are:
1. Executing Dynamic Python Code Blocks
exec() allows running arbitrary Python code dynamically generated at runtime:
dynamic_code = """
for x in range(5):
print(‘Iterated value:‘, x)
"""
exec(dynamic_code) # Prints iterated values
This provides good flexibility to generate and execute logic at runtime.
2. Importing Modules Dynamically
You can use exec() to load Python modules based on varying conditions:
module_name = # Determine from user input
import_str = f‘import {module_name}‘
exec(import_str)
# Now module loaded dynamically
This technique is used by test frameworks and module importers.
3. Implementing Embeddable Interpreters
Tools that embed Python engines for code execution use exec() for executing statements:
statements = """
a = 5
b = 10
print(‘Sum =‘, a + b)
"""
output = ""
exec(statements, globals(), locals())
# Capture output externally
print(output) # Prints computed sum
This methodology enables implementing code interpreters and REPL interfaces.
Key Differences Between Eval and Exec
While eval() and exec() serve similar purposes, they have some key differences:
-
eval()evaluates and returns the result of a single Python expression.exec()runs a code block with one or more Python statements. -
eval()can only execute expressions, whileexec()can run full-blown compound statements likeforloops,ifconditions etc. -
eval()returns the value from evaluation of the expression.exec()always returnsNoneregardless of executed code.
Consider the differences demonstrated below:
x = 1
eval_result = eval(‘x + 1‘) # Returns 2
print(eval_result)
exec_result = exec(‘x += 1‘) # Modifies x, returns None
print(exec_result) # Prints None
In most cases where output capture is not required, exec() ends up being more useful.
Security Concerns and Risk Mitigation
While the capabilities of eval() and exec() are very powerful, they can also open up security risks if used carelessly since they will execute ANY code provided to them. Some risks to be aware of are:
1. Arbitrary Code Execution Risks
If an attacker can somehow pass input to eval() or exec(), malicious code may get executed:
user_input = # Unsanitized user input
eval(user_input)
# Code exploitation if user_input is malicious
This can lead to system compromise, data exfiltration, DoS attacks etc. depending on executed code.
2. Privilege Escalation Vulnerabilities
Code executed via eval()/exec() inherits permissions of calling script. This can elevate attack privileges:
with open(‘main.py‘) as f:
code = f.read()
exec(code) # Runs with main.py privileges
Such threats necessitate proper design precautions when using these functions.
3. Command Injection Attacks
Calls to powerful system libraries may allow command injection if inputs are not sanitized:
user_input = # Unescaped dynamic value
eval(‘os.system(‘ + user_input + ‘)‘)
# Commands can be injected if not filtered
Only properly sanitized inputs should be passed to evaluation/execution.
Some key mitigation strategies should be adopted to secure usage include:
1. Restricted Namespaces
Limit namespaces exposed via eval()/exec():
clean_namespaces = {‘math‘: math}
eval(‘math.cos(2)‘, clean_namespaces) # Safer
This reduces accessible functionality.
2. Allow Listing Imports
Whitelist modules permitted for import:
allowed_modules = [‘math‘, ‘sys‘]
user_code = "import math"
# Check if module allowed before exec
exec(user_code)
Prevents unsafe imports.
3. Sandboxing Untrusted Code
For external code, run eval()/exec() in isolated environments like Docker containers or VMs to limit damages from malicious executions.
4. Prevalidation Before Execution
Check code logic, early return if issues found:
if ‘__‘ in code:
return # Possible exploitation
try:
compiled = compile(code, ‘<string>‘, ‘exec‘)
except SyntaxError:
return # Invalid Python
# Run code only after validation checks
These best practices make eval() and exec() usage more secure.
Recommended Usage Guidelines
Based on our analysis, here are some evidence-based guidelines for securely leveraging eval() and exec():
-
Constrain namespaces: Restrict the globals/locals exposed to minimize unintended impacts
-
Validate/sanitize before evaluation: Check code logic, input types, early return on issues
-
Handle exceptions cleanly: Catch syntax errors, zero divisions etc. with try/except blocks
-
Use allow listing for imports: White list modules/packages permitted for import
-
Match strictest privileges required: Reduce process permissions before code execution
-
Run sandboxed for untrusted code: Use containers or virtual environments to isolate
-
Prefer
eval()where possible: Only useexec()when you require compound statements.
Adopting these prescribing coding techniques enables developing robust and resilient applications using eval() and exec().
Expert Guidance on Advanced Usage
As an expert developer, I often get queries on niche advanced usage of Python‘s dynamic execution functions – like manipulating namespaces, optimized exception handling, multi-threading implications etc.
Let me share some insightful tips and tricks that require a deeper expertise level to properly apply in projects.
Optimized Namespace Control
Fine grained control over namespaces, as highlighted earlier, is key for security. Here is an optimized technique to fully restrict namespaces:
clean_globals = {
# Explicitly initialize builtins
"__builtins__": {
‘len‘: len,
‘dict‘: dict,
# ... Include only safe builtins
},
# Other permitted globals
}
clean_locals = {}
eval(code, clean_globals, clean_locals)
This provides lot more control than just __builtins__ = None.
Granular Exception Handling
It helps to catch specific exception types for handling different errors:
try:
result = eval(expr)
except SyntaxError:
# Invalid expression
print("Expression error")
except ZeroDivisionError:
# Divide by zero scenario
print("Math error")
More generic except Exception would mask underlying root causes.
Multithreading Challenges
Dynamic execution in multi-threaded apps requires explicit synchronization when modifying shared state:
import threading
lock = threading.Lock()
def call_eval(expr):
lock.acquire()
try:
# Critical section
return eval(expr)
finally:
lock.release()
# Ensure synchronized access
Otherwise timing oddities can cause race conditions.
Hope these tips help enhance your expertise level on advanced Python exploitation! Let me know if you have any other specific queries.
Conclusion
In closing, eval() and exec() provide extremely versatile techniques for executing Python code dynamically at runtime in programs. However, their capabilities also introduce security risks if leveraged carelessly.
I hope this expert guide gave you a comprehensive overview of best practices and mitigation strategies for securely applying dynamic code execution in Python while unlocking immense power.
Some key takeaways around properly leveraging these builtin functions include:
- Understand clear use cases on when
eval()/exec()suit your requirements - Limit namespace exposures selectively to only required minimum
- Validate and sanitize external code before execution
- Handle errors cleanly with specific
exceptblocks - Run security-sensitive code in isolated environments
- Prefer
eval()only for expressions, minimizeexec()usage
Mastering dynamic execution while adopting sound techniques allows building robust Python applications.
Hope you enjoyed this advanced guide! Do provide any feedback for improving coverage or readability. I‘m happy to produce more such expert content for practicing developers looking to skill up.


