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
.