2

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.

Buyuk
  • 1,094
  • 1
  • 8
  • 23
peter
  • 1,034
  • 1
  • 9
  • 23
  • from `b.py` you should be able to just `import c` no? – Tom Myddeltyn May 17 '16 at 13:57
  • [This](https://www.youtube.com/watch?v=0oTh1CXRaQ0) might be quite useful for handling imports, although a bit long. – quapka May 17 '16 at 13:58
  • @peter I have another post which covers creating and importing custom modules. If it helps you, please upvote. http://stackoverflow.com/questions/37072773/how-to-create-and-import-a-custom-module-in-python/37074372#37074372 –  May 17 '16 at 13:58
  • 2
    How do you execute this scripts? Ive recreted the situation and imported a module. In my case it worked quite ok. – Buyuk May 17 '16 at 14:01
  • @Buyuk I am sorry for the previous incorrect question description. It has been modified now. – peter May 18 '16 at 03:18
  • @peter http://stackoverflow.com/questions/12229580/python-importing-a-sub-package-or-sub-module – Buyuk May 18 '16 at 10:17
  • PS. In the future, you can remove old version of the question, it should still be visible in the edit history in case anyone wanted to see it. – Buyuk May 18 '16 at 10:18
  • @Buyuk I guess your link is not relevant because I have only used absolute import here? I have no idea how is `import a.b.d as d` different from `from a.b import d`. – peter May 19 '16 at 06:07
  • @quapka Great video! I watched it through, but still had no answer to my question :( – peter May 19 '16 at 06:09

1 Answers1

3

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.

Heda Wang
  • 306
  • 3
  • 9