Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between "import lib.foo" and "import lib.foo as f" in Python

I'm puzzled by how circular imports are handled in Python. I've tried to distill a minimal question and I don't think this exact variant was asked before. Basically, I'm seeing a difference between

import lib.foo

and

import lib.foo as f

when I have a circular dependency between lib.foo and lib.bar. I had expected that both would work the same: the (possibly half-initialized) module would be found in sys.modules and put into the local namespace. (From testing I noticed that import lib.foo really puts lib into the local namespace — okay, with that syntax I'll do lib.foo.something anyway.)

However, if lib.foo is already in sys.modules, then import lib.foo as f tries to access foo as an attribute on lib and raises AttributeError. Why does the behavior (seemingly) depend on the presence in sys.modules?

Also, where is this behavior documented? I don't feel that the Python import statement reference explains this behavior, or at least I couldn't extract it :-)

All in all I'm trying to change a code base to use the oft recommended style where you import modules, not symbols in the modules:

from project.package import moduleA
from project.package import moduleB

But that fails when there are circular imports between the two modules. I had expected it to work as long as the top-level definitions in the two modules don't depend on each other (e.g., no subclass in moduleB with a base class in moduleA).

Test script:

#!/bin/sh
rm -r lib; mkdir lib

touch lib/__init__.py

cat > lib/foo.py <<EOF
# lib.foo module
print '{ foo'
#import lib.bar # works
import lib.bar as b # also works
#from lib import bar # also works
print 'foo }'
EOF

cat > lib/bar.py <<EOF
# lib.bar module
print '{ bar'
#import lib.foo # works
import lib.foo as f # AttributeError: 'module' object has no attribute 'foo'
#from lib import foo # ImportError: cannot import name foo
print 'bar }'
EOF

python -c 'import lib.foo'
like image 511
Martin Geisler Avatar asked Oct 11 '12 09:10

Martin Geisler


1 Answers

When you say import lib.foo as f what you are telling Python to do is the equivalent of import lib.foo; f = lib.foo at the bytecode level. You end up with an AttributeError in the issue being asked because lib in this case doesn't have foo set yet as an attribute. Python hasn't completed its import of lib.foo when it tries to do the assignment and thus has not set the attribute yet on lib; look at the Python 3.3 source for import where you can see where a module is loaded vs. couple statements farther down where a module is set on its parent.

This is where you end up with some circular import issues. You need to let the import for lib.foo complete before you try to access lib.foo, else the attribute on lib simply won't exist yet for the bytecode to access. This might be why you think you are not using any top-level definitions directly in your code but in actuality you are through your import statements.

like image 89
Brett Cannon Avatar answered Oct 15 '22 11:10

Brett Cannon