Question

Two files, file1.py, file2.py. Can someone please explain why line 1.8 will print spam == 1 and not spam == 2 ? I've read all python circular import posts but I still don't understand this. Also, I don't understand why file1 is interpreted again from scratch when imported from file2. I was under the impression modules are not loaded again once they've been imported once.

# file1.py
print('1.1: initializing spam to 0')
spam = 0
spam += 1
print('1.4: spam == {}'.format(spam))
print('1.5: importing file2')
import file2
print('1.7: imported file2')
print('1.8: spam == {}'.format(spam))  # why 1 and not 2?
print('FILE 1 PARSED')  # why is this executed twice?

# file2.py
print('\t\t\t2.1: importing file1')
import file1
print('\t\t\t2.3: imported file1, file1.spam == {}'.format(file1.spam))
file1.spam += 1
import file1
print('\t\t\t2.6: print from file2: file1.spam == {}'.format(file1.spam))
print('\t\t\tFILE 2 PARSED')

The output I'm getting is this:

1.1: initializing spam to 0
1.4: spam == 1
1.5: importing file2
            2.1: importing file1
1.1: initializing spam to 0
1.4: spam == 1
1.5: importing file2
1.7: imported file2
1.8: spam == 1
FILE 1 PARSED
            2.3: imported file1, file1.spam == 1
            2.6: print from file2: file1.spam == 2
            FILE 2 PARSED
1.7: imported file2
1.8: spam == 1
FILE 1 PARSED

PS. I appreciate circular imports are to be avoided, but I need to understand the logic.

Was it helpful?

Solution

Python import more or less goes as

  1. look if there's a module with that name already, if so return it immediately
  2. the module has not been imported, so create the module object and put it in the registry immediately, then load the source and execute it

What happens if you execute file1 is that the execution start as a main program then

  1. file1 code imports file2: since it was not present so the module object is created and execution of file2 begins
  2. file2 code imports file1: since file1 was not present as a module then a module object is created and execution of file1 begins (as a module!)
  3. file1 code (as a module) imports file2: the module is already present so the partially constructed file2 module object is returned immediately
  4. file1 as a module completes execution and file2 resumes after import
  5. file2 module completes execution and file1 as main program resumes after import
  6. the program completes

In other words there will be two instances of spam: one inside the "main program" file1.py and one inside the module file1.

Consider this simplified test case:

# p1.py
import p2, sys
p1d = {}
print id(p1d), id(sys.modules['p1'].p1d)

# p2.py
import p1
print "HERE"

running p1.py you will get as output something like

18465168 18465168
HERE
17940640 18465168

the first two numbers are equal because the print is executed from the p1 module while the second two numbers are different because print is executed from p1 main program, that is a different instance.

PS: if you write intentionally code that does this (i.e. a main that indirectly import another instance of itself as a module) you should be put in jail :-)

OTHER TIPS

When file1.py is run as the main script, it is not the file1 module. It is the __main__ module. Pretend it's a completely separate file that happens to look exactly like the file1 module, because that's how Python treats it.

Python runs file1.py as __main__, which imports file2, which imports file1. Since the __main__ module is not considered the same as the file1 module, file1.py runs again.

When file1 imports file2, since file2 is already being imported, file1 fetches the half-constructed file2 module object and keeps going. It does not give file2 a chance to increase file1.spam. This is why the first instance of line 1.8 prints 1.

When execution gets back to file2, it increases file1.spam, so line 2.6 prints 2. However, __main__.spam is still 1. This is why when execution gets back to __main__, the second instance of line 1.8 also prints 1.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top