155

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?

shino
  • 4,562
  • 5
  • 38
  • 57

4 Answers4

151

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).

When you assign a new value to a, you are just changing 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 will actually be 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 a killer when trying to reload a module from the interactive interpreter.

Marco
  • 8,958
  • 1
  • 36
  • 56
aaronasterling
  • 68,820
  • 20
  • 127
  • 125
  • 1
    Thanks! Your answer probably just saved me several hours of looking for the culprit. – jnns Feb 13 '19 at 11:53
  • Looks like even the `bar.bar.a` approach won't help much in most use cases. It's probably a weak idea to mix compile or module loading and runtime assignments because you'll end up confusing yourself (or others that use your code). Especially in languages that behave somewhat inconsistently about it, like python arguably does. – matanster Jun 27 '20 at 09:41
33

One source of difficulty with this question is that you have a program named bar/bar.py: import bar imports either bar/__init__.py or bar/bar.py, depending on where it is done, which makes it a little cumbersome to track which a is bar.a.

Here is how it works:

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
# … where bar = bar/bar.py (as if bar were imported locally from __init__.py)

and defines a new variable (bar/__init__.py:a, if you wish). Thus, your from bar import a in __init__.py binds name bar/__init__.py:a to the original bar.py:a object (None). This is why you can do from bar import a as a2 in __init__.py: in this case, it is clear that you have both bar/bar.py:a and a distinct variable name bar/__init__.py:a2 (in your case, the names of the two variables just happen to both be a, but they still live in different namespaces: in __init__.py, they are bar.a and a).

Now, when you do

import bar

print bar.a

you are accessing variable bar/__init__.py:a (since import bar imports your bar/__init__.py). This is the variable you modify (to 1). You are not touching the contents of variable bar/bar.py:a. So when you subsequently do

bar.foobar()

you call bar/bar.py:foobar(), which accesses variable a from bar/bar.py, which is still None (when foobar() is defined, it binds variable names once and for all, so the a in bar.py is bar.py:a, not any other a variable defined in another module—as there might be many a variables in all the imported modules). Hence the last None output.

Conclusion: it is best to avoid any ambiguity in import bar, by not having any bar/bar.py module (since bar.__init__.py makes directory bar/ a package already, that you can also import with import bar).

Eric O. Lebigot
  • 91,433
  • 48
  • 218
  • 260
21

To put another way: Turns out this misconception is very easy to make. It is sneakily defined in the Python language reference: the use of object instead of symbol. I would suggest that the Python language reference make this more clear and less sparse..

The from form does not bind the module name: it goes through the list of identifiers, looks each one of them up in the module found in step (1), and binds the name in the local namespace to the object thus found.

HOWEVER:

When you import, you import the current value of the imported symbol and add it to your namespace as defined. You are not importing a reference, you are effectively importing a value.

Thus, to get the updated value of i, you must import a variable that holds a reference to that symbol.

In other words, importing is NOT like an import in JAVA, external declaration in C/C++ or even a use clause in PERL.

Rather, the following statement in Python:

from some_other_module import a as x

is more like the following code in K&R C:

extern int a; /* import from the EXTERN file */

int x = a;

(caveat: in the Python case, "a" and "x" are essentially a reference to the actual value: you're not copying the INT, you're copying the reference address)

Mark Gerolimatos
  • 2,424
  • 1
  • 23
  • 33
  • Actually, I find the Python's way of `import` much more cleaner than Java's, because namespaces/scopes shall always be kept properly separated and never interfere with each other in unexpected ways. Like here: Altering a binding of an object to a name in a namespace (read: assing something to a module's global property) never affects other namespaces (read: the reference which was imported) in Python. But does so in Java, etc. In Python you only need to understand what is imported, while in Java, you also must understand the other module in case, it alters this imported value later on. – Tino Nov 12 '17 at 11:20
  • 3
    I have to disagree quite strenuously. Importing/including/using has a long history of using the reference form and not the value form in nust about every other language. – Mark Gerolimatos Nov 12 '17 at 11:29
  • 8
    Dammit, I hit return… there is this an expectation of import by reference (as per @OP). In fact, a new Python programmer, no matter how experienced, has to be told this in the "look out" sort of way. That should never happen: based on common usage, Python took the wrong path. Create an"import value" if need be, but don't conflate symbols with values at time of import. – Mark Gerolimatos Nov 12 '17 at 11:33
0

When I need to change certain configuration variables in a module or package, I normally utilize it into a mutable object. I think it is the namespace and reference concept in Python. In your case, I'll make the following changes.

bar/bar.py

class Config:
    a = None

def foobar():
    print(Config.a)

bar/__init__.py

from .bar import Config, foobar

main.py

import bar

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