peter
peter

Reputation: 1054

Is it circular import when using absolute import and __init__.py

I have the following file structure, each with at most one line of code (shown below):

a
├── b
│   ├── c.py          import a.b.d as d
│   ├── d.py
│   └── __init__.py   from a.b.c import *
├── __init__.py
└── main.py           import a.b as b

By running python -m a.main, I get the following error:

Traceback (most recent call last):
  File "/usr/lib/python2.7/runpy.py", line 162, in _run_module_as_main
    "__main__", fname, loader, pkg_name)
  File "/usr/lib/python2.7/runpy.py", line 72, in _run_code
    exec code in run_globals
  File "/tmp/test/a/main.py", line 1, in <module>
    import a.b as b
  File "a/b/__init__.py", line 1, in <module>
    from a.b.c import *
  File "a/b/c.py", line 1, in <module>
    import a.b.d as d
AttributeError: 'module' object has no attribute 'b'

I am not sure if this is caused by circular import. If I change import a.b.d as d to from a.b import d, there is no error any more.

Upvotes: 2

Views: 404

Answers (1)

Heda Wang
Heda Wang

Reputation: 306

The AttributeError is caused by the as in the import statement in file c.py.

The whole process is like:

  1. main.py created module a, added it to sys.modules and initialized it;
  2. main.py created module a.b, added it to sys.modules and began to execute its code;
  3. b/__init__.py (a and a.b are already in sys.modules) created module a.b.c, added it to sys.modules and begin to execute its code;
  4. b/c.py created module a.b.d, added it to sys.modules, executed its code, added it as an attribute 'd' of module a.b, then tried but failed to bind a.b.d to name d. The problem is that module a.b was not done initializing yet, so the attribute 'b' was not in module a.

WHY

To understand this, you should know that an import statement does two things (in both Python 2 and Python 3).

  1. Find a module or modules, and initialize it or them if necessary;
  2. Define a name in the local namespace and bind it to a certain module.

Module Finding

The former one calls the __import__ hook, which loads the module and initializes it. In Python 2 the hook is imputil.ImportManager._import_hook by default, and it works like this.

  1. Check if the module is in sys.modules;
  2. If not, find the module and get its code;
  3. Create the module and add it to sys.modules;
  4. Run the code within the module's namespace;
  5. Return the module.

If the statement is like import a.b.c, the module finding process will recursively find module a, a.b, a.b.c, and keep track of them in sys.modules. If module a.b is returned, it is set as an attribute 'b' of module a. Finally, the module finding process will return the top module a.

If the statement is like from a.b import c,d, the outcome is a little different. In this case the bottom module (i.e. module a.b) will be returned.

Name Binding

If you use an import [module] statement, the name of the top module will be bound to the return value (which is the top module).

If you use an import [module] as [name] statement, then [name] will be bound to the bottom module (by accessing the attributes of the top module).

If you use an from [module] import [identifier], then the name of the bottom module will be bind to the return value (which in from import statement is the bottom module).

Example
import a.b.c          # a <- <module 'a'>
import a.b.c as c     # c <- <module 'a'>.b.c
from a.b import c     # c <- <module 'a.b'>.c

In your question, the import statement in c.py occurs when module a.b is half initialized and is not yet registered in module a's attributes. So import as will encounter a problem when binding a.b.c to c. However, since module a.b is already registered in sys.modules, using from import will not encounter such a problem.

Upvotes: 3

Related Questions