Reputation: 11440
Is it possible to access exec-provided globals dictionary from within a function, if the function was defined outside of the exec-ed code (and thus already bound to different __globals__
)?
In other words, is there a way to make the following example work?
def f():
log("Hi")
exec('f()', {'f': f, 'log': print})
In general, is it possible to substitute the __globals__
of a function?
Upvotes: 0
Views: 223
Reputation: 281843
This is a pretty weird thing to do, but it's doable.
Your exec call executes the statement f()
in the provided globals. It does not execute the body of f
in the provided globals. The provided globals are being used in the wrong stack frame. To access those globals from f
, you can use stack inspection:
import inspect
def f():
log = inspect.currentframe().f_back.f_globals['log']
log('Hi')
exec('f()', {'f': f, 'log': print})
If you want to execute the body of f
with the provided globals rather than just gaining access to the globals, you need to make a copy of f
with your own custom globals:
import types
my_f = types.FunctionType(f.__code__,
{'log': print},
f.__name__,
f.__defaults__,
f.__closure__)
my_f()
The function type constructor is sort of documented; it's not in the online docs, but it is documented in the function type's docstring:
function(code, globals[, name[, argdefs[, closure]]])
Create a function object from a code object and a dictionary.
The optional name string overrides the name from the code object.
The optional argdefs tuple specifies the default argument values.
The optional closure tuple supplies the bindings for free variables.
Upvotes: 2
Reputation:
Not sure if I am fully correct about the explanation. In short, the example cannot work in Python 3.
The reason is in the combination of 2 circumstances: [1] - exec
is a function in Python 3, [2] - the code you try to execute contains function call.
When you provide globals
optional argument to the function exec
it is local scope of this very function. So the following example works:
exec('log("Hi")', {'log': print})
But the original one does not. Because in the original example you call the function f
. It has its own local scope. What Python does? It checks global scope (actual global scope of the program) and innermost scope (local scope of the function f
). Both scopes lack for log
and you get NameError
.
You can get the very same behavior (the same error) with two regular functions:
def f():
log("Hi")
def f_():
log = print
f()
f_()
Upvotes: 0