Updated answer from Dec 12, 2018:
Here is another (better?) way. Basically, we manually read(), compile(), and then exec() the entire source file. When we call exec(), we can pass in an alternative global dictionary. The entire file is read, compiled, and executed twice, but we do achieve the desired result.
def baz ():
global foo
print ( foo ) # should print the string 'foo'
foo = 5
print ( foo ) # should print 5
print ( bar ) # should print the string 'bar'
class CustomGlobals ( dict ):
def __getitem__ ( self, k ):
if k in self: return self .get ( k )
if hasattr ( self .get ( '__builtins__' ), k ):
# we raise KeyError to avoid clobbering builtins
raise KeyError
return k
def main ():
with open ( __file__, 'rb' ) as f:
source = f .read() # re-read __file__
code = compile ( source, __file__, 'exec' ) # re-compile __file__
g = CustomGlobals ( globals() )
g [ '__name__' ] = None # prevent infinite recursion
exec ( code, g ) # re-exec __file__ against g
g [ 'baz' ] () # call the re-complied baz function
if __name__ == '__main__': main()
The above approach is superior (IMO) because we do not need to nest code in a string, and because error messages will contain correct line numbers.
Original answer from Nov 29, 2018:
If the Python code in question comes from a string (rather than a standard .py file), then you can exec() the string and provide a custom global dictionary as follows:
code = '''
print ( foo ) # should print the string 'foo'
foo = 5
print ( foo ) # should print 5
print ( bar ) # should print the string 'bar'
'''
class CustomDict ( dict ):
def __init__ ( self, other ): super() .__init__ ( other )
def __getitem__ ( self, k ):
if k in self: return self .get ( k )
if hasattr ( self .get ( '__builtins__' ), k ):
# we raise KeyError to avoid clobbering builtins
raise KeyError
return k
exec ( code, None, CustomDict ( globals() ) )
As desired, the above outputs:
foo
5
bar
I have not found any way to "mutate" a module to achieve the same result for code that is in that module. If anyone knows of a way to mutate a module, I would be very happy to see it.
(Perhaps a module could read its own source code into a string, then compile that string in the context of a custom global dict, and then inject the result into sys.modules? In other words, the module would replace itself with a clone of itself, but with a different global dict. Hmmm.)
Aside: There are ways to simulate __getitem__() and __getattr__() on a module. And as of Python 3.7, you can simply and directly define a __getattr__() function in the module. However, as far as I can tell, none of these techniques can hook into the lookup of global variables. Sources: