4

Can someone explain this behavior?

A.py:

import B
values = []

if __name__ == "__main__":
    values.append('something')
    print(values)
    B.printValues()

B.py:

import A

def printValues():
    print(A.values)

Result:

['something']
[]

I expected:

['something']
['something']
Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
nil_dib
  • 51
  • 1
  • 2
    The `import A` in `B` is *from the file*, and **doesn't** run the `if __name__ == "__main__"` block (the *whole point* of which is that it doesn't run on `import`), so `B` sees the original (empty) `values`. – jonrsharpe Jan 20 '15 at 16:38
  • possible duplicate of [Why is Python running my module when I import it, and how do I stop it?](http://stackoverflow.com/questions/6523791/why-is-python-running-my-module-when-i-import-it-and-how-do-i-stop-it) – senshin Jan 20 '15 at 16:40
  • I'm with OP. This is how I see it: 1. `A` imports `B`, which in turn imports `A`. 2. `'something'` is appended to `values`. 3. `B.printValues` is called. 4. `A.values` is accessed, which should be `['something']`, but isn't. How come `B`'s `A.values` differs from `A`'s `values`? – Aran-Fey Jan 20 '15 at 16:51
  • interesting. I guess jonrsharpe is correct, but I thought that modules were only loaded once, so B's reference to A was the same as A itself, including the values list. Must not be the case after all. On a side note, you probably know that A import B, B import A is a circular import and not necessarily best practice. – JL Peyret Jan 20 '15 at 16:53
  • 1
    ah, I wonder if the import A aspect isn't the key here. A, when run is __main__, it doesn't consider itself loaded in the modules. When B runs it imports A, which does load in the modules. It happens to be the same A.py, but one is main executing pgm, the other one is a module and will not be reloaded. id(values) gives two different outputs, from A and B. – JL Peyret Jan 20 '15 at 16:58
  • @JLPeyret: You're right, `A` is executed twice, once due to `import A`. – Aran-Fey Jan 20 '15 at 17:06
  • @senshin. related, but not a duplicate. the link you are referring to is concerned with the ' __name__ = "main" ' guard code to limit execution to main only. As I see it, the OP's observed behavior is more generic in nature and concerns namespacing in sys.modules vs in '__main__', as Hiroki says. Sorry, not totally clear with my explanation, but best I can find to express. – JL Peyret Jan 20 '15 at 17:23

3 Answers3

2

This is what happens:

  1. A imports B. This causes the code in B to be executed:
    1. A is imported. Because this is the first time A is being imported (A is not in sys.modules), all the code in A is executed.
      1. import B is executed. This does not execute the code in B, because Bis already in sys.modules.
      2. The empty list values is created.
      3. the if __name__=='__main__' block is not executed, leaving values empty.
    2. B now has a reference to a module A that differs from the main module. You can confirm this by adding import __main__; print __main__ is A to B. It will print False.
Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
1

Yes, I am pretty sure A as main is not the same as the one in import A which ends up in sys.modules. So A exists twice, once as main, once as a module. Added a third module, C and you see that the B to C semantics meet your expectation - B and C share a common A.

A.py

import sys
print len(sys.modules), "len(sys.modules):A top"
import B, C
print len(sys.modules), "len(sys.modules):A after import B, C"

values = []


if __name__=="__main__":
    values.append('something')
    print "A:", values, 'id:',id(values)
    B.printValues()
    C.printValues()

B.py

import sys
print len(sys.modules), "len(sys.modules):B top"
import A
print len(sys.modules), "len(sys.modules):C after A import"

def printValues():
    print "B:", A.values, "id:", id(A.values)

C.py

import sys
print len(sys.modules), "len(sys.modules):C top"
import A
print len(sys.modules), "len(sys.modules):C after A import"

def printValues():
    print "C:", A.values, "id:", id(A.values)    

and this all outputs:

 42 len(sys.modules):A top
 43 len(sys.modules):B top
 44 len(sys.modules):A top
 45 len(sys.modules):C top
 45 len(sys.modules):C after A import
 45 len(sys.modules):A after import B, C
 45 len(sys.modules):C after A import
 45 len(sys.modules):A after import B, C
 A: ['something'] id: 4493313232
 B: [] id: 4493269616
 C: [] id: 4493269616               
JL Peyret
  • 10,917
  • 2
  • 54
  • 73
0

In short, it is a kind of closure.

you can add print any place to observe import behavior.

A.py

print("before import B")
import B
print("start execute A as " + __name__)

values = []

if __name__=="__main__":
    values.append('something')
    print "values=" + str(values)
    B.printValues()
    B.appendValue("hello")
    B.printValues()
    print "values=" + str(values)

print("finished execute A as " + __name__)

B.py

print("start runnig B")
import A

def printValues():
    print "A.values=" + str(A.values)

def appendValue(new_value):
    A.values.append(new_value)

print "A.values=" + str(A.values)

print("finish running B")

it gives result

before import B
start runnig B
before import B
start execute A as A
finished execute A as A
A.values=[]
finish running B
start execute A as __main__
values=['something']
A.values=[]
A.values=['hello']
values=['something']
finished execute A as __main__

A.values seen from B.py is another instance of data. It is not A.py's values. there is another namespace between combination of program & __name__.

Hiroki Kumazaki
  • 389
  • 1
  • 5