A code object doesn’t have a reference to the builtins and globals scopes that are required to execute it. Thus exec()
and eval()
take the globals to use as an option, which defaults to the current globals. The builtins scope can be set as __builtins__
in globals, else it defaults to the current builtins.
A function object references its builtins and globals scopes, which are exposed as its __builtins__
and __globals__
read-only attributes. It doesn’t matter from where a function is called. It always uses the same builtins and globals. For example:
>>> b = vars(builtins).copy()
>>> g = {'__builtins__': b}
>>> exec('f = lambda: spam', g)
>>> g['f'].__builtins__ is b
True
>>> g['f'].__globals__ is g
True
>>> b['spam'] = 'eggs'
>>> g['f']()
'eggs'
Note that the __builtins__
reference in globals no longer matters to the function object because it has its own reference.
>>> del g['__builtins__']
>>> g['f']()
'eggs'