While playing around with compile(), the marshal module, and exec. I've encountered some confusing behavior. Consider simple.py
def foo():
print "Inside foo()..."
def main():
print "This is a simple script that should count to 3."
for i in range(1, 4):
print "This is iteration number", i
foo()
if __name__ == "__main__":
main()
When I run this script using exec like this
with open('simple.py', 'r') as f:
code = f.read()
exec code
it gives the expected output.
This is a simple script that should count to 3.
This is iteration number 1
This is iteration number 2
This is iteration number 3
Inside foo()...
However, when if I introduce compile(), marshal.dump(), and marshal.load() like this
import marshal
def runme(file):
with open(file, "r") as f:
code = marshal.load(f)
exec code
with open("simple.py", "r") as f:
contents = f.read()
code = compile(contents, "simple.py", "exec")
with open("marshalled", "w") as f:
marshal.dump(code, f)
runme("marshalled")
it prints the beginning of the expected output and then errors out
This is a simple script that should count to 3.
This is iteration number 1
This is iteration number 2
This is iteration number 3
Traceback (most recent call last):
File "./exec_within_function.py", line 17, in <module>
runme("marshalled")
File "./exec_within_function.py", line 8, in runme
exec code
File "simple.py", line 15, in <module>
main()
File "simple.py", line 12, in main
foo()
NameError: global name 'foo' is not defined
Why does it say that foo is not defined?
In order to understand, I tried using dir() like this
import simple # imports simple.py
dir(simple)
and as expected, it shows that foo is defined.
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'foo', 'main']
I've also noticed that when I use dis.dis() on the deserialized code object (read via marshal.load()), the only thing I see is the LOAD_NAME and CALL_FUNCTION for main(), but when I do it with import like this
import dis, sys
import simple
dis.dis(sys.modules["simple"])
it gives me the entire disassembly as expected.
I've even looked at some of the code that python uses for compiling and although I think import uses some sort of lookup table for definitions, I'm not sure what the difference is with compile() that's causing this behavior.