eval(), exec() and locals()
eval(source, /, globals=None, locals=None) and exec(source, /, globals=None, locals=None, *, closure=None) compile
source when called and run it in the namespace CPython would: the module globals for a call at module scope, a
snapshot of the function’s locals (PEP 667) plus the module globals inside a function, or the dicts passed as
arguments.
Functions defined by a snippet under a globals dict resolve their globals through that dict at every call:
ns = {'x': 1}; exec('def f(): return x', ns); ns['x'] = 2; ns['f']() returns 2.
The snippet can call host functions and raise into the caller; top-level await, including in class bodies, is rejected.
sourcemust be astror UTF-8bytes. There are no code objects and nocompile(), so a code object cannot be passed.closuremust beNone(the default); non-Nonevalues raiseTypeError.globalsdefaults toNone, using the caller’s globals; non-Nonevalues must be adict.localsdefaults toNone, usingglobalswhen an explicit globals dict is passed, or the caller’s locals otherwise. Non-Nonevalues must be adict; CPython accepts any mapping.
__builtins__is never inserted into aglobalsdict.exec('x = 1', ns)leavesns == {'x': 1}, where CPython adds a'__builtins__'entry; reading it in Monty raisesNameErrorunless it was explicitly supplied or assigned.- Module dunders under a
globalsdict raiseNameErrorunless the dict defines them. CPython resolves them through thebuiltinsmodule, soexec('print(__name__)', {})printsbuiltins. Without aglobalsdict the snippet uses Monty’s read-only module-level dunders, so assigning to them, including through aglobaldeclaration, raisesNotImplementedError. The assignment is rejected before any snippet statement runs; its traceback points to the assignment’s line. This restriction does not apply to explicit globals dictionaries. - A host-served name first read inside a snippet is cached as a module global, as for any other read of an undefined
global; see name lookups.
A snippet run under a
globalsdict never asks the host: only the dict and the builtins are consulted, which is what CPython does withexec(source, {}). del namedoes not parse anywhere, snippets included (see language.md).
- A
SyntaxErrorin the snippet carries CPython’s(<string>, line N)suffix, but the message before it is the parser’s own wording, and the traceback has no extraFile "<string>", line Nframe for the syntax error. - Frames of snippet code appear in tracebacks as
File "<string>", line N, in <module>with no source line, as in CPython.
- At module scope
locals()returns a freshdictof the bound module globals, not the module namespace itself: writes to it are not reflected,locals() is locals()isFalse, and the module-level dunders are absent. - Inside a function it is a snapshot of the named locals, with captured variables read through their cells, as in
CPython 3.13 and later.
A function with both
*argsand keyword-only parameters lists*argsbefore the keyword-only parameters; CPython lists the keyword-only parameters first. - Inside a snippet it is the snippet’s
localsdict, or itsglobalsdict when the two are the same, as in CPython.
Snippet source is never type-checked.
A session with type checking enabled checks the code it is fed; eval() and exec() compile their strings at
runtime, where no checker runs, so a call a host function stub would reject is only caught by the host function itself.
Parsing and compilation count against max_feed_duration and max_turn_duration.
Once a snippet starts executing, its source, literals, functions and bytecode remain allocated for the rest of the
session, counted against max_memory, even if execution raises an exception.
A snippet rejected before execution, including one refused by the recursion limit, retains none of its compilation
products.
See resource_limits.md.