What is the difference between Python's eval() and exec()?
answer
- One hands a value back, one does not
- The split follows Python's grammar
- Assignments and loops need the other builtin
- exec() returns None every single time
- compile() has matching 'eval' and 'exec' modes
basics
~10 seval() evaluates one expression and returns its value. exec() runs statements - assignments, loops, imports, definitions - and always returns None. Hand eval() a statement and it raises SyntaxError.
solid answer
~40 s`eval()` takes a single Python **expression**, something that produces a value, and returns that value. `exec()` takes any block of **statements** and returns `None`; you observe its effect through the namespace it wrote into, not through a return value. Both accept a string, bytes, or a pre-compiled code object, and both take optional `globals` and `locals` mappings that decide which names the code can see and where new bindings land (since 3.13 those two may also be passed as keyword arguments). The split mirrors `compile()`'s mode argument: `'eval'` compiles one expression, `'exec'` compiles a sequence of statements. So `eval("cpm * 1000")` is fine, while `eval("total = 1")` raises `SyntaxError` because an assignment is a statement. If the string is really just data, `ast.literal_eval()` parses it without executing arbitrary code.
code
python · 6 linesvalue = eval("2 + 3 * 4")
print(value) # 14
result = exec("total = 2 + 3")
print(result) # None
print(total) # 5go deeper
Recall the one-line split: eval() takes a single expression and gives you its value, exec() takes statements and gives you nothing. Be ready to say which one runs x = 1 and which one runs x + 1.
Explain the mechanics: both accept a string, bytes or a code object, both take globals and locals mappings, and compile()'s 'eval' and 'exec' modes mirror the two builtins. Show how you retrieve a value that exec() computed.
Show judgement about when neither belongs in the code at all. Name ast.literal_eval() for data strings, note that pre-compiling avoids re-parsing on a hot path, and be clear that the namespace you pass is the only channel exec() has.
Own the policy question: what in your codebase is allowed to execute strings at runtime, what the alternatives are (a parsed configuration format, a restricted expression grammar, a plugin entry point), and how you keep this out of code paths fed by outside input.
## The grammar line the two builtins sit on Python's grammar splits source into **expressions** and **statements**. An expression produces a value: `2 + 3`, `cpm * 1000`, a call, a comprehension, a conditional expression, even an assignment expression `(w := 5)`. A statement *does* something: `x = 5`, `for`, `if`, `import`, `def`, `class`, `return`, `with`. Every expression can stand alone as a statement (its value is then discarded), but no statement can appear where a value is required. `eval()` and `exec()` are the two builtins that run code arriving as text at runtime, and they sit on opposite sides of that line. ## eval() `eval(source, globals=None, locals=None)` parses `source` as **one expression**, evaluates it, and **returns the result**. The source may be a `str`, a `bytes` object, or a code object previously produced by `compile(..., mode='eval')`. Because the whole point is the returned value, `eval()` is the right tool when you want a number, a container, or an object back: ```python print(eval("3 * 4 + 1")) # 13 print(eval("max(bids)", {"bids": [3, 9, 4]})) # 9 ``` Give it anything the grammar calls a statement and the parse fails before evaluation ever starts: ```python eval("total = 1") # SyntaxError: invalid syntax eval("import math") # SyntaxError ``` One subtlety worth knowing: an *assignment expression* is still an expression, so `eval("(w := 5)")` succeeds and binds `w` into the globals mapping it was given. That is a real binding, not a copy. ## exec() `exec(source, globals=None, locals=None)` parses `source` as **a sequence of statements**, runs them, and **always returns `None`**. Anything you want back has to be read out of the namespace afterwards: ```python ns = {} exec("floor = 2\nbid = floor * 3", ns) print(ns["bid"]) # 6 ``` A very common beginner error is expecting `exec()` to behave like a REPL and hand back the value of the last line. It does not; the value of a bare expression statement is discarded, exactly as it is inside a normal function body. Because `exec()` parses statements, indentation matters. A string that carries leading indentation - typical when the source came out of a triple-quoted template inside an indented block - raises `IndentationError`. `textwrap.dedent()` is the usual fix. `eval()` is more forgiving only in that leading whitespace on its single line is stripped. ## What they share Both take the same two optional namespace arguments, and both accept a code object as well as text. The two builtins are best understood as *drivers* over a code object whose behaviour was fixed at compile time: `compile(src, filename, 'eval')` yields a code object that produces a value, `compile(src, filename, 'exec')` yields one that does not. The mode, not the builtin, is what decides. Consequently `eval()` will happily run an `'exec'`-mode code object - it just returns `None`, because there is no result to hand back. Both also default their namespaces to the caller's scope when you pass nothing, which is why `exec("total = 5")` typed at module level really does create a module-level name, while the same call inside a function does not create a function local. ## Choosing, and choosing neither In day-to-day code the honest answer is usually *neither*. Three questions sort it out: 1. **Is the string plain data** - a number, a list, a dict of literals? Then `ast.literal_eval()` is the right call. It parses the text and builds only literals and containers; `ast.literal_eval("len('abc')")` raises `ValueError` rather than calling anything. Note it is deliberately narrow: even `ast.literal_eval("1 + 2")` is rejected, because arithmetic is not a literal. 2. **Do you need a value back from a small formula?** That is `eval()`'s job, and pre-compiling the formula with `compile(..., 'eval')` avoids re-parsing it on every call. 3. **Do you need to define or bind things?** That is `exec()`, and you should pass an explicit dict so the results land somewhere you control. And if the string came from outside your process, the interesting question stops being *eval or exec* and becomes whether you should be executing it at all - both run arbitrary code with the full power of the interpreter.
- If exec() always returns None, how do you get a computed value back out of the code it ran?Pass your own dictionary as the globals mapping and read the key out afterwards: `ns = {}; exec("bid = 6", ns); ns["bid"]`. There is no other channel - the return value is always `None`, and the value of a bare expression statement inside the string is discarded. If what you want is a single value, use `eval()` instead so the result comes back directly.
- Can eval() run a code object that was compiled in 'exec' mode?Yes. The mode is baked into the code object at `compile()` time, and both builtins will execute a code object of either mode. An `'exec'`-mode object run through `eval()` executes its statements normally and returns `None`, because there is no expression result to produce. The practical consequence is that the mode argument, not the choice of builtin, decides whether you get a value.
- When should you reach for ast.literal_eval() instead of eval()?Whenever the string is data rather than code - a number, a tuple, a list or dict of literals, a boolean, `None`. `ast.literal_eval()` parses the text and constructs only literals and containers, raising `ValueError` on anything else, so `ast.literal_eval("len('abc')")` fails rather than calling a function. It is deliberately narrow: even `"1 + 2"` is rejected, since arithmetic is not a literal.
saying these in an interview costs you the question
- Says exec() returns the value of its last statement
- Thinks eval() can run an assignment, loop or import
- Treats exec() as just another name for eval()
- Uses eval() to parse a config string that is plain data
- Assumes a string passed to eval() is compiled once and cached