I made a file named challenge.py with this code in it:
import challenge
def main():
print('Circular much...')
challenge.main()
From this I was expecting python to raise an error due to the circular import of importing the file which is running but I found that on python 3.7 & 3.8 this file runs and prints out Circular much... twice. I would understand once as that would mean that the rest of the file when it is importing itself isn't running and I would understand a recursion error as it ran challenge.main() infinitely down the stack but I don't understand why it prints it twice and stops?
Tracing this through:
import challenge
Ok, we'll import challenge.py. Here we go...
import challenge
We're already importing challenge.py, so we won't do it again.
def main():
print('Circular much...')
Defined the function main() in the namespace challenge. Cool.
challenge.main()
Now call the function main() in the namespace challenge. That prints Circular much.... There's your first print.
Now we're back in the main module again.
def main():
print('Circular much...')
This defines the function main() in the global namespace (which never gets called).
challenge.main()
This calls the function main() in the namespace challenge, again printing Circular much....
And we're done. Two prints of your message.
It may be instructive to look at sys.modules.
For example:
import sys
def check_modules():
print("seeing what we have...")
if "challenge" in sys.modules:
module = sys.modules["challenge"]
print(f"sys.modules contains {module.__file__}")
if hasattr(module, "challenge"):
print("... and it contains the variable 'challenge'")
if module is module.challenge:
print("... pointing back at itself")
if hasattr(module, "main"):
print("... and it contains the variable 'main'")
print()
print("Before import:")
check_modules()
import challenge
print("After import:")
check_modules()
def main():
print('Circular much...')
print("After declaring 'main':")
check_modules()
challenge.main()
gives:
Before import:
seeing what we have...
Before import:
seeing what we have...
sys.modules contains /tmp/challenge.py
After import:
seeing what we have...
sys.modules contains /tmp/challenge.py
... and it contains the variable 'challenge'
... pointing back at itself
After declaring 'main':
seeing what we have...
sys.modules contains /tmp/challenge.py
... and it contains the variable 'challenge'
... pointing back at itself
... and it contains the variable 'main'
Circular much...
After import:
seeing what we have...
sys.modules contains /tmp/challenge.py
... and it contains the variable 'challenge'
... pointing back at itself
... and it contains the variable 'main'
After declaring 'main':
seeing what we have...
sys.modules contains /tmp/challenge.py
... and it contains the variable 'challenge'
... pointing back at itself
... and it contains the variable 'main'
Circular much...
As you can see, the module gets added to sys.modules at the start of the import, before the actual code in the module being imported is run. If the import statement is reached when the module file is present in sys.modules, then this is sufficient to prevent it being imported again, which is why there is only a single level of recursion.
Once the import is finished, the result of the import (a module object) is assigned to the variable challenge, and the if module is module.challenge test in the code confirms that it is a reference to the same module as the one in which the name is created (as importing a module already imported simply reuses the same module object already created).
Now as regards the question of how the call to challenge.main works: precisely because challenge is simply a reference to the current module, this means that when the function definition is executed, thereby creating the name main inside the current module object, the same function object to which it points can equally be accessed as challenge.main instead of main.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With