Search code examples
pythonnamespacesprogram-entry-pointpython-module

Why does this not run into an infinite loop?


This code was picked up from an answer in this thread What does if __name__ == "__main__": do?

# Suppose this is foo3.py.

def functionA():
    print("a1")
    from foo3 import functionB
    print("a2")
    functionB()
    print("a3")

def functionB():
    print("b")

print("t1")
print("m1")
functionA()
print("m2")
print("t2")

I think the code is executed as follows (when executed as main python3 foo3.py):
1. Prints t1
2. Prints m1
3. Enters functionA and prints a1
4. Imports functionB from foo3, thus running foo3 again. Goes back to step 1
Can you help me correct my analysis?


Solution

  • It does not "run foo3 again", it runs the foo3.py script again. The first time foo3.py was running was to produce the module __main__, the second time to produce the module foo3.py.

    The behaviour is in fact (almost) as if you had a file named __main__.py and another named foo3.py both with these exact same content and you then run python __main__.py. This is what is happening.

    Only, Python fakes it so that it looks as if the program started from a script named __main__.py no matter what the actual Python file was. The only telltale sign to contrary is that __file__ would tell the filename of the actual script, i.e. /spam/ham/eggs/foo3.py.


    The reason why it does not go to an infinite loop is that import looks for a module with the given name in sys.modules - if it is already present there it does not execute any new files. Upon startup Python will create an entry for __main__ in sys.modules, and the code of the startup script (foo3.py) is executed within the scope of this module.

    Then when it executes the statement import foo3 it will check if foo3 has an entry in sys.modules. As it is not there, a new empty module named foo3 is created, placed into sys.modules, and the code of foo3.py is executed within the scope of this new empty module.

    It eventually executes the import 2nd time. This time there is foo3 in sys.modules, so importing does not create or load any more scripts, just returns the already-loaded module.

    To get the "infinite" loop you can delete the already-imported module reference from sys.module prior to importing foo3 again:

    import sys
    
    def functionA():
        print("a1")
    
        if 'foo3' in sys.modules:
            del sys.modules['foo3']
    
        from foo3 import functionB
        print("a2")
        functionB()
        print("a3")
    
    def functionB():
        print("b")
    
    print("t1")
    print("m1")
    functionA()
    print("m2")
    print("t2")
    

    And when run you'll get

      [....]
      File ".../foo3.py", line 7, in functionA
        from foo3 import functionB
      File ".../foo3.py", line 17, in <module>
        functionA()
      File ".../foo3.py", line 7, in functionA
        from foo3 import functionB
    RuntimeError: maximum recursion depth exceeded while calling a Python object