exec()
Execute arbitrary Python STATEMENTS from a string. Never on untrusted input. Rarely the right tool.
Common call
exec("x = 1")
Returns
None — side effects only
Replaces
nothing — usually you should NOT reach for exec
Watch out
runs ARBITRARY Python; side effects on the caller's scope; no return value
exec(sourcesource — A string containing Python statements (or a compiled code object). Can be a multi-line block — assignments, def, class, import, control flow.type: str | code · required, globalsglobals — Optional globals dict for execution. If None, uses the caller's globals.type: dict · default: None=None, localslocals — Optional locals dict. Defaults to the globals dict.type: dict · default: None=None)
→ None
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| source | str | code | yes | A string containing Python statements (or a compiled code object). Can be a multi-line block — assignments, def, class, import, control flow. |
| globals | dict | no (None) | Optional globals dict for execution. If None, uses the caller's globals. |
| locals | dict | no (None) | Optional locals dict. Defaults to the globals dict. |
Return value
None — Always returns None. exec RUNS Python statements — assignment, def, class, import, control flow — with side effects in the given namespace. If globals / locals are omitted, uses the caller's scope.
Common patterns
Dynamic class or function generation (framework code)
Occasionally used by ORMs and dataclass-like frameworks.
body = f"def __init__(self, {args}):\n " + inits exec(body, ns)
Trusted config that includes Python
Some tools (Django settings, IPython config) are Python files exec'd as a config.
with open("settings.py") as f: exec(f.read(), config_globals)
For DATA, do not exec
Use JSON, YAML, TOML, or a real config format — never exec a data string.
import json config = json.loads(text)
For dispatch, use a dict of callables
exec is almost never needed for dynamic function selection.
HANDLERS = {"add": handle_add} HANDLERS[cmd](*args)
Examples
1. Assignment
exec("x = 1")
x
Returns
12. Custom namespace
ns = {}
exec("x = 42", ns)
ns["x"]
Returns
423. Multi-line
exec("a = 1\nb = 2\nprint(a + b)")
Returns
3 # printed4. Returns None
result = exec("x = 1")
Returns
None # always5. Function def
exec("def f(): return 42")
f()
Returns
426. Untrusted RCE
exec(user_input)
Returns
DANGER: arbitrary code executionPitfalls
1. exec is ARBITRARY CODE EXECUTION — never on untrusted input
Everything eval's warning says goes double for exec. eval at least tries to be an expression evaluator; exec runs anything. On user input, you have handed the attacker a Python interpreter.
Attacker's dream
exec(request.form["config"])
the attacker owns your process
Never do this
use a real config format
2. exec ALWAYS returns None
A common expectation from other languages: "the last expression is the return". Not in Python. exec returns None; get results by reading the namespace it wrote into.
Assumed return
x = exec("42")
x is None
Read the namespace
ns = {} exec("result = 42", ns) x = ns["result"]
42
3. Inside a function, exec cannot modify local variables directly
Python compiles function locals into a fixed slot layout. exec writes into a namespace dict; the compiler does not know about it, so the function's named locals are unaffected.
Locals unchanged
def f(): exec("x = 1") return x
NameError: name x is not defined
Use a namespace
def f(): ns = {} exec("x = 1", ns) return ns["x"]
1
4. Rarely the right tool — reach for it last
If you find yourself wanting exec, first check: can I use a dict of callables? An ast walk? A real parser? importlib for dynamic imports? These are almost always safer and clearer.
Reflex reach for exec
exec(dynamic_code)
usually avoidable
Structured alternative
HANDLERS[key](args)
safer
When to use
Use it
- Framework code that generates classes or functions from templates (dataclasses, ORMs)
- Trusted config files that ARE Python (Django settings, IPython)
- REPL implementations where you WANT arbitrary Python
- Almost nothing else
Reach for something else
- Any input from users, network, or files → do NOT exec
- Data serialization → JSON / pickle / dataclasses
- Dynamic dispatch → dict of callables
- Dynamic imports → importlib.import_module
Notes
Complexity
Compile + execution — variable
Return
None
CPython impl
Python/bltinmodule.c :: builtin_exec
Memory
Allocates code objects; side effects on namespace
Thread-safe
Depends entirely on the executed code
FAQ
eval takes an EXPRESSION and returns its value. exec takes STATEMENTS (arbitrary Python code) and returns None. Use eval when you need a value; use exec when you need side effects on a namespace.
History
3.0
exec became a function (was a statement in Python 2).