TLDR: Provide explicit globals dictionaries to execute code "as if" it were run at top-level.
Python uses lexical scoping, which roughly means that names are looked up as per the nesting in source code. For example, a nested function can access an outer function's variable.
def foo():
bar = 12
def qux():
print(bar) # nested scope accesses outer scope
return qux
Notably, this kind of name lookup happens at source code compilation time – the compiler knows that both foo and qux access bar, so it must be made available to both. The inner function is directly compiled to look at the variable of the outer function.
If instead a function is not nested, its name lookup to non-local variables automatically goes to global scope.
def qux():
print(bar) # top-level scope accesses global scope
This is a problem when we exec code in a function: A definition such as def main(): this() is compiled to lookup this at global scope, but exec runs in the current local scope of the function.
In all cases, if the optional parts are omitted, the code is executed in the current scope.
Thus, this is looked up globally but defined locally.1 The compiler parses def test and the exec code separately, so it does not know that lookups are meant to be treated as nested.
As a result, the lookup fails.
In order to exec code "as if" it were run at top-level, it is sufficient to supply an explicit globals – for example a fresh namespace {} or the actual globals().
def test():
exec("""
def this():
print('this')
def main():
this()
main()
""", {})
# ^^ globals namespace for exec
test()
This means the code is now run in (its own) global namespace, which is where the lookup of functions searches as well.
1This can be checked by printing globals()/locals() and by inspecting the instructions of the function. dis.dis(main) in the exec would produce:
6 0 LOAD_GLOBAL 0 (this)
2 CALL_FUNCTION 0
4 POP_TOP
6 LOAD_CONST 0 (None)
8 RETURN_VALUE
globalstoexec. What exactly do you want to achieve with this, or is just purely out of academic interest?thisandmainare both defined in the local scope oftest, butmainstill looks in the global scope for the definition ofthis. When you pass adict, that is used as the namespace, so the call tomainwill look there instead for the definition ofthis.execbefore worrying about this, though. Just because (you think) you can useexecdoesn't mean you should be using it.