views:

60

answers:

2

Suppose I have a package named bar, and it contains bar.py:

a = None

def foobar():
    print a

and __init__.py:

from bar import a, foobar

Then I execute this script:

import bar

print bar.a
bar.a = 1
print bar.a
bar.foobar()

Here's what I expect:

None
1
1

Here's what I get:

None
1
None

Can anyone explain my misconception?

+2  A: 

you are using from bar import a. a becomes a symbol in the global scope of the importing module (or whatever scope the import statement occurs in). So when you assign a new value to a, you just change which value a points too, not the actual value. try to import bar.py directly with import bar in __init__.py and conduct your experiment there by setting bar.a = 1. This way, you are actually modifying bar.__dict__['a'] which is the 'real' value of a in this context.

It's a little convoluted with three layers but bar.a = 1 changes the value of a in the module called bar that is actually derived from __init__.py. It does not change the value of a that foobar sees because foobar lives in the actual file bar.py. You could set bar.bar.a if you wanted to change that.

This is one of the dangers of using the from foo import bar form of the import statement: it splits bar into two symbols, one visible globally from within foo which starts off pointing to the original value and a different symbol visible in the scope where the import statement is executed. Changing a where a symbol points doesn't change the value that it pointed too.

This sort of stuff is killer when trying to reload a module from the interactive interpreter.

aaronasterling
+2  A: 

The key to understanding what happens is to realize that in your __init__.py,

from bar import a

in effect does something like

a = bar.a  # … as if bar were imported

and defines a new variable (bar/__init__.py:a, if you wish).

Thus, when you do

import bar

print bar.a

you are accessing the copy bar/__init__.py:a (since import bar imports your bar/__init__.py). And when you subsequently do

bar.foobar()

you call bar/bar.py:foobar(), which accesses the original bar/bar.py:a, which is None. Hence the last None output.

Thus, your from bar import a in __init__.py creates a new copy named a of the original bar.py:a. This is why you can do from bar import a as copy_a in __init__.py: in this case, you have bar/bar.py:a and a distinct copy bar/__init__.py:copy_a.

EOL