The eval() built-in function parses a Python expression string or compiled code object, evaluates it, and returns the computed result.
Expression Evaluation & Scope Control: Unlike exec() which executes multi-statement code and returns None, eval() expects a single expression and returns its calculated value. You can control symbol visibility and isolate execution using custom globals() and locals() dictionaries. When evaluating expressions repeatedly inside loops, precompile them first using compile() to eliminate parsing overhead, or inspect output strings via repr().
The eval() function accepts up to three arguments:
# Formal syntax of built-in eval()
result = eval(expression, globals=None, locals=None)
| Parameter | Type | Default | Operational Role |
|---|---|---|---|
expression |
str or code object |
Required | A single valid Python expression string or a bytecode object compiled in 'eval' mode. |
globals |
dict |
None |
Optional dictionary representing the global namespace. Defaults to current global scope. |
locals |
mapping |
None |
Optional mapping object representing local variables. Defaults to the globals dictionary. |
eval() parses and computes arithmetic expressions, boolean comparisons, and string transformations dynamically:
# 1. Basic dynamic expression evaluation
# Math calculation
calc = eval("(25 * 4) + (100 // 5) - 15")
print("Math result: ", calc)
# Boolean comparison
check = eval("10 > 5 and 'py' in 'python'")
print("Comparison result: ", check)
# String manipulation
text = eval("'plus2net'.upper() + ' tutorial'")
print("String result: ", text)Terminal Output:
Math result: 105
Comparison result: True
String result: PLUS2NET tutorial
By default, eval() has access to the caller's global and local namespaces. You can sandbox execution by supplying explicit dictionaries. To suppress built-in functions, set {"__builtins__": {}}:
# 2. Controlling execution scope via globals and locals
import math
# A. Providing specific functions in custom namespace
allowed_scope = {"fsum": math.fsum, "factor": 2.5}
res1 = eval("fsum([10, 20, 30]) * factor", allowed_scope)
print("Scoped calculation:", res1)
# B. Suppressing builtins entirely
strict_scope = {"__builtins__": {}}
try:
eval("sum([1, 2, 3])", strict_scope)
except NameError as err:
print("Caught blocked builtin:", err)Terminal Output:
Scoped calculation: 150.0
Caught blocked builtin: name 'sum' is not defined
Evaluating raw strings inside tight loops is inefficient because Python re-parses the expression on every iteration. Precompiling with compile() in 'eval' mode delivers major performance gains:
# 3. Performance boost: precompiling repeated expressions
import time
expr_str = "x ** 2 + 2 * x + 1"
n = 50000
# Raw string evaluation
t0 = time.perf_counter()
for x in range(n):
_ = eval(expr_str)
raw_time = time.perf_counter() - t0
# Precompiled bytecode evaluation
code_obj = compile(expr_str, "<speedup>", "eval")
t1 = time.perf_counter()
for x in range(n):
_ = eval(code_obj)
comp_time = time.perf_counter() - t1
print(f"Raw string eval: {raw_time:.4f} s")
print(f"Precompiled bytecode:{comp_time:.4f} s ({raw_time / comp_time:.1f}x faster)")Terminal Output:
Raw string eval: 0.1380 s
Precompiled bytecode:0.0210 s (6.6x faster)
eval(). Restricting globals with {"__builtins__": {}} is not safe because attackers can navigate object inheritance chains (e.g., via ().__class__.__bases__[0].__subclasses__()) to import dangerous modules and execute arbitrary shell commands.
When you only need to parse raw data literals (strings, numbers, tuples, lists, dicts, booleans, and None), use the standard library ast.literal_eval(). It evaluates data structures without executing arbitrary code:
# 4. Safe data parsing using ast.literal_eval()
import ast
# Safely parsing string literals into Python objects without code execution
data_str = "{'course': 'Python', 'modules': 12, 'active': True}"
data_dict = ast.literal_eval(data_str)
print("Parsed dictionary:", data_dict)
print("Data type: ", type(data_dict).__name__)
# Malicious attempts raise ValueError instead of executing
try:
ast.literal_eval("__import__('os').system('ls')")
except ValueError as e:
print("Blocked unsafe input:", e)Terminal Output:
Parsed dictionary: {'course': 'Python', 'modules': 12, 'active': True}
Data type: dict
Blocked unsafe input: malformed node or string on line 1: <ast.Call object at ...>
To safely evaluate mathematical formulas entered by users, inspect the Abstract Syntax Tree (AST) before compilation to ensure only whitelisted arithmetic nodes are present:
# 5. Production Recipe: Secure arithmetic formula evaluator
import ast
def safe_calculator(formula: str, variables: dict) -> float:
"""Evaluates arithmetic expressions while blocking functions and calls."""
tree = ast.parse(formula, mode='eval')
for node in ast.walk(tree):
if not isinstance(node, (ast.Expression, ast.BinOp, ast.UnaryOp,
ast.Constant, ast.Name, ast.Load,
ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Pow)):
raise ValueError(f"Disallowed operation: {type(node).__name__}")
code = compile(tree, "<safe_math>", "eval")
return eval(code, {"__builtins__": {}}, variables)
# Safe formula test
price_formula = "unit_price * qty * (1 - discount_rate)"
context = {"unit_price": 45.0, "qty": 10, "discount_rate": 0.10}
final_cost = safe_calculator(price_formula, context)
print("Computed total cost:", final_cost)Terminal Output:
Computed total cost: 405.0
Understanding the strict differences between expressions and statements prevents common runtime errors:
# 6. Common traps: eval() vs. exec() & statement restrictions
# Trap 1: eval() cannot evaluate statements (only expressions)
try:
eval("x = 100")
except SyntaxError as e:
print("eval statement trap: ", e.msg)
# Trap 2: eval() always returns a value; exec() returns None
expr_result = eval("10 + 20")
exec_result = exec("y = 10 + 20")
print("eval() returned: ", expr_result)
print("exec() returned: ", exec_result)Terminal Output:
eval statement trap: invalid syntax
eval() returned: 30
exec() returned: None
Practice dynamic expression evaluation, scope sandboxing, and AST validation in our interactive companion Jupyter Notebook:
Explore interactive code cells, benchmark loop speedups, test custom scope 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 eval() mechanics across all scoping modes:
# 7. Self-verifying unit test suite for eval()
import ast
# 1. Verify standard arithmetic expression evaluation
assert eval("15 * 3 + 5") == 50, "arithmetic eval failed"
# 2. Verify custom globals scope isolation
scope = {"a": 20, "b": 3}
assert eval("a * b", scope) == 60, "custom scope eval failed"
# 3. Verify builtins suppression
try:
eval("len([1, 2])", {"__builtins__": {}})
assert False, "builtins suppression failed"
except NameError:
pass
# 4. Verify precompiled code execution
compiled = compile("pow(2, 5)", "<test>", "eval")
assert eval(compiled) == 32, "compiled eval failed"
# 5. Verify ast.literal_eval safety
parsed = ast.literal_eval("[1, 2, 3]")
assert parsed == [1, 2, 3], "literal_eval failed"
print("All eval() unit assertions successfully verified!")Terminal Output:
All eval() unit assertions successfully verified!
eval() evaluates a single expression and returns its value. exec() executes multi-statement code blocks and returns None.
No. Variable assignments like x = 10 are statements, not expressions, and trigger an immediate SyntaxError in eval().
No. Attackers can traverse object classes to access built-ins. For data parsing, always prefer ast.literal_eval().
Precompiling with compile() parses the code once into bytecode, making repeated loop executions much faster.
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.