Search code examples
pythonpython-3.xdynamic-code

Using arbitrary variables as namespaces for `exec`


With this test code using exec (using Python 3.4):

vals = {}
exec('def fun(): print("Hello from fun")', vals)
exec('def main(): fun()', vals)
vals['main']()

The output is:

Hello from fun

But I did not expect this to work, since I assumed that fun and main where interpreted as separate code pieces without a common namespace to resolve the reference in main to fun.

So how can execution of main resolve the reference to fun?


Addition based understanding the issue. With print of id for vals and globals, it becomes clear that the two functions sees the same globals:

vals = {}
print('id(vals):', id(vals))
exec('def fun(): print("fun id(globals()):", id(globals())); print("Hello from fun")', vals)
exec('def main(): print("main id(globals()):", id(globals())); fun()', vals)
vals['main']()

Which gives:

id(vals): 32271016

main id(globals()): 32271016

fun id(globals()): 32271016

Hello from fun

So the vals is used as globals for the code in exec, thus giving the connection, as @Dunes and other comments described. Thanks.


Solution

  • By providing vals to both exec functions, you have provided the common namespace. The second argument to exec is a dictionary to use for global references in any code that is executed. When the first statement is executed it creates fun and stores it in the global namespace (vals). Thus, when when main tries to find fun, it finds that it is not a local variable and so tries to lookup fun in its globals (which is also vals). Since fun exists in vals the lookup works and the function retrieved and called. If gave each exec its own dict then this wouldn't work. If you don't provide vals then the current globals that exec is called in is used as the globals (so this would still work).