The AttributeError
is caused by the as
in the import statement in file c.py
.
The whole process is like:
main.py
created module a
, added it to sys.modules
and initialized it;
main.py
created module a.b
, added it to sys.modules
and began to execute its code;
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;
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).
- Find a module or modules, and initialize it or them if necessary;
- 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.
- Check if the module is in
sys.modules
;
- If not, find the module and get its code;
- Create the module and add it to
sys.modules
;
- Run the code within the module's namespace;
- 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.