Skip to content
advanced Phase 17 · Python Metaprogramming

Dynamic Code Execution

Understand eval, exec, compile, and their security implications.

45m
0 problems
Topic Progress 0%

eval, exec, and compile

eval, exec, and compile

Three builtins form the dynamic-execution toolkit:

code = compile("1 + 2", "<calc>", "eval")   # → code object; modes: eval/exec/single
eval(code)                                  # expressions only → returns 3
eval("x + 1", {"x": 10})                    # globals dict supplies names

exec("y = 5")                               # statements → returns None, side effects only
d = {}
exec("y = 5", d); d["y"]                    # 5 — result lands in the dict

eval demands an expression and yields its value; exec accepts statements (defs, loops, imports) and yields None. compile separates parsing from execution — precompile hot snippets once, then eval the code object repeatedly. eval also accepts an exec-mode code object only through exec; the reverse fails with a SyntaxError.

Scope Inside Functions

Scope Inside Functions

exec inside a function cannot write local variables — the compiler decided the function’s fast locals before the string ran:

def f():
    exec("a = 1")        # a does NOT appear here
    return locals().get("a")      # depends on the dict passed in

def g():
    scope = {}
    exec("a = 1", scope)          # explicit dict = reliable channel
    return scope["a"]

Pass a single dict as both globals and locals and assignments become visible to code sharing that dict:

scope = {"__builtins__": {}}
exec("result = sum(values)", scope)   # scope["result"] after run

Two dicts (globals separate from locals) send assignments only to the locals mapping — reads still see globals, which produces confusing partial environments. Prefer explicit data flow over exec-driven name injection.

Security and Safe Alternatives

Security and Safe Alternatives

Never evaluate untrusted input. There is no reliable sandbox: even with {"__builtins__": {}}, reachable objects lead to subclasses of object and then to ().__class__.__mro__[1].__subclasses__(), which exposes file and process primitives. Every "restricted eval" has been escaped.

Safe substitutions:

Need Use
Parse literals (numbers, lists, dicts) ast.literal_eval(s) — accepts only literal syntax
Data interchange json.loads
Config expressions A purpose-built grammar (e.g. ast walk with an allowlist)
Plugins/callables importlib over vetted names, not eval(f"{name}()")

Interview angle: say plainly that eval executes arbitrary code with the caller’s privileges, show ast.literal_eval as the narrow exception, and name one real-world incident class (template/config injection).

Quiz

1. What does exec("x = 1") return?

Question 1 options

2. Why doesn’t exec("a = 1") define a local in an enclosing function?

Question 2 options

3. Safest way to parse a JSON-looking string from untrusted input?

Question 3 options

Flashcards

Question

eval vs exec

Answer

eval = expression → value; exec = statements → None with side effects.

Question

compile modes

Answer

"eval" for expressions, "exec" for statements, "single" for interactive lines — returns a code object.

Question

exec scope rule

Answer

Writes go into the dict you pass; function locals are unreachable without an explicit scope dict.

Question

ast.literal_eval

Answer

Parses literals (str/num/list/dict/tuple/bool/None) safely — the sanctioned eval replacement.

Revision Notes

Key Takeaways

  • 1. eval/exec/compile separate parsing from execution but grant full interpreter power
  • 2. Function-scope dynamics require an explicit dict; never rely on exec to create locals
  • 3. There is no safe eval for untrusted input — use literal_eval, json, or a restricted grammar

Interview Tips

  • • Give the sandbox-escape reasoning (__subclasses__ chain) when asked why eval is unsafe
  • • Show the two-dict vs one-dict scope distinction if pressed on dynamic execution details

Cheat Sheet

eval(expr)→value; exec(stmts)→None; compile(src, "", "eval"|"exec"); exec writes only passed dicts; no eval sandbox → ast.literal_eval.

Continue Learning