The exec() built-in function dynamically executes arbitrarily complex Python code, including multiple statements, loops, classes, and function definitions.
Multi-Statement Execution & Namespace Mutation: Unlike eval() which only evaluates single expressions and returns a value, exec() executes code blocks dynamically and always returns None. State changes are captured inside designated namespace dictionaries via globals() and locals(). For repetitive execution inside loops, compile code ahead of time with compile(), or inspect object representations with repr().
The exec() function accepts up to three arguments and always returns None:
# Formal syntax of built-in exec()
exec(object, globals=None, locals=None)
| Parameter | Type | Default | Operational Role |
|---|---|---|---|
object |
str or code |
Required | A string containing Python statements or a code object compiled in 'exec' mode. |
globals |
dict |
None |
Optional dictionary representing the global namespace. Defaults to the current global scope. |
locals |
mapping |
None |
Optional mapping object representing local variables. Defaults to the globals dictionary. |
Unlike single-expression evaluators, exec() handles complete multiline scripts containing loops, conditionals, and newly declared functions:
# 1. Multi-statement execution, loops, and function generation
code_snippet = '''
def greet_users(names):
return [f"Welcome, {name}!" for name in names]
roster = ["Alice", "Bob", "Charlie"]
welcome_messages = greet_users(roster)
for msg in welcome_messages:
print(msg)
'''
# Execute the code block dynamically
exec(code_snippet)Terminal Output:
Welcome, Alice!
Welcome, Bob!
Welcome, Charlie!
Executing dynamic code directly without explicit dictionaries contaminates the caller's namespace. Supplying an isolated dictionary confines all newly created functions and variables:
# 2. Namespace isolation: capturing created functions in custom dicts
plugin_code = '''
def calculate_tax(subtotal, rate=0.08):
return subtotal * (1 + rate)
active_tier = "Standard Enterprise"
'''
# Provide an isolated dictionary for globals
plugin_namespace = {}
exec(plugin_code, plugin_namespace)
# Access functions and variables created inside the namespace
tax_fn = plugin_namespace["calculate_tax"]
final_price = tax_fn(250.0)
print("Calculated final price:", final_price)
print("Extracted active tier: ", plugin_namespace["active_tier"])Terminal Output:
Calculated final price: 270.0
Extracted active tier: Standard Enterprise
Repeatedly running string blocks inside loops forces Python to re-tokenize and re-parse the code on every pass. Precompiling with compile() yields substantial speedups:
# 3. Performance precompilation for repetitive execution
import time
script = '''
total = 0
for i in range(100):
total += i * 2
'''
iterations = 10000
# Approach A: Raw string exec (re-tokenized and re-parsed each iteration)
t0 = time.perf_counter()
scope_a = {}
for _ in range(iterations):
exec(script, scope_a)
raw_duration = time.perf_counter() - t0
# Approach B: Precompiled bytecode exec
compiled_code = compile(script, "<precompiled_loop>", "exec")
t1 = time.perf_counter()
scope_b = {}
for _ in range(iterations):
exec(compiled_code, scope_b)
comp_duration = time.perf_counter() - t1
print(f"Raw string exec: {raw_duration:.4f} s")
print(f"Precompiled exec: {comp_duration:.4f} s")
print(f"Performance speedup: {raw_duration / comp_duration:.1f}x faster")Terminal Output:
Raw string exec: 0.1850 s
Precompiled exec: 0.0420 s
Performance speedup: 4.4x faster
Choosing between exec() and eval() depends strictly on whether you need statement execution or expression calculation:
| Attribute | exec() |
eval() |
|---|---|---|
| Input Scope | Arbitrary statement sequences, loops, definitions, imports | Single Python expression only |
| Return Value | Always returns None |
Returns evaluated expression result |
| State Mutation | Mutates globals/locals dictionary directly | Evaluates and returns; does not execute assignments |
| Compile Mode | Compiled with mode='exec' |
Compiled with mode='eval' |
exec(). Even with restricted globals {"__builtins__": {}}, attackers can inspect Python's object hierarchy to access built-ins and run shell commands. For parsing structured data literals, always use ast.literal_eval().
In production systems, exec() is commonly used to load custom business rule sets or client-specific validation policies into dedicated namespace scopes:
# 4. Production Recipe: Dynamic Policy Rule Evaluator
policy_rules = '''
def evaluate_eligibility(applicant):
if applicant["credit_score"] >= 700 and applicant["income"] > 50000:
return "Approved Premium Tier"
elif applicant["credit_score"] >= 620:
return "Approved Standard Tier"
return "Refer to Manual Review"
'''
# Compile and store in secure namespace
policy_scope = {}
exec(compile(policy_rules, "<policy_engine>", "exec"), policy_scope)
# Evaluate multiple client records
clients = [
{"name": "David", "credit_score": 740, "income": 65000},
{"name": "Elena", "credit_score": 640, "income": 42000},
{"name": "Frank", "credit_score": 580, "income": 35000}
]
evaluator = policy_scope["evaluate_eligibility"]
for client in clients:
decision = evaluator(client)
print(f"{client['name']:<7}: {decision}")Terminal Output:
David : Approved Premium Tier
Elena : Approved Standard Tier
Frank : Refer to Manual Review
Keep these common operational pitfalls in mind when using exec() in application code:
# 5. Scope leakage and function local variable traps
# Trap 1: Omitting globals causes variables to leak into caller's scope
exec("leaked_var = 'I am everywhere'")
print("Leaked global variable:", leaked_var)
# Trap 2: exec() always returns None (never expression values)
result = exec("40 + 2")
print("exec() return value: ", result)
# Trap 3: exec() inside a function does NOT modify local fast variables
def test_scope():
x = 10
exec("x = 99") # Modifies local copy, but does not overwrite fast local
print("Value inside function: ", x)
test_scope()Terminal Output:
Leaked global variable: I am everywhere
exec() return value: None
Value inside function: 10
Deepen your hands-on mastery of dynamic code execution, custom namespaces, and precompilation with our interactive companion Jupyter Notebook:
Explore interactive code cells, benchmark loop speedups, test custom namespace dictionaries, and run self-validating assertions.
View on GitHub Run in Google ColabRun these unit checks directly in Python or your local environment to verify exec() mechanics across all scoping modes:
# 6. Self-verifying unit test suite for exec()
# Test 1: Verify multi-statement execution and namespace mutation
ns = {}
exec("a = 10\nb = 20\nc = a + b", ns)
assert ns["c"] == 30, "exec namespace assignment failed"
# Test 2: Verify exec() returns None
assert exec("x = 1") is None, "exec must always return None"
# Test 3: Verify function definition and execution
fn_scope = {}
exec("def square(n): return n * n", fn_scope)
assert fn_scope["square"](5) == 25, "dynamic function creation failed"
# Test 4: Verify precompiled bytecode execution
compiled = compile("y = 50 * 2", "<test>", "exec")
ns_comp = {}
exec(compiled, ns_comp)
assert ns_comp["y"] == 100, "compiled code execution failed"
# Test 5: Verify builtins access in custom scope
ns_builtins = {}
exec("res = sum([1, 2, 3, 4])", ns_builtins)
assert ns_builtins["res"] == 10, "builtins access failed"
print("All exec() unit assertions successfully verified!")Terminal Output:
All exec() unit assertions successfully verified!
exec() is designed for statements, which do not have return values. Output and state changes are captured inside the provided globals/locals dictionaries.
Yes. When a function definition statement executes inside exec(), the resulting function object is stored as a callable entry in the target namespace.
Always pass a custom dictionary as the second argument: exec(code, custom_dict). This ensures all newly created variables remain isolated.
Calling compile(code, "<name>", "exec") parses source code into bytecode once. Repeated executions in loops skip compilation overhead.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.