为什么 Python 的 `from` 导入语句形式会绑定模块名称?

Why might Python's `from` form of an import statement bind a module name?

我有一个 Python 项目,其结构如下:

testapp/
├── __init__.py
├── api
│   ├── __init__.py
│   └── utils.py
└── utils.py

除具有以下代码的 testapp/api/__init__.py 外,所有模块都是空的:

from testapp import utils

print "a", utils

from testapp.api.utils import x

print "b", utils

testapp/api/utils.py 定义 x:

x = 1

现在我从根导入 testapp.api:

$ export PYTHONPATH=$PYTHONPATH:.
$ python -c "import testapp.api"
a <module 'testapp.utils' from 'testapp/utils.pyc'>
b <module 'testapp.api.utils' from 'testapp/api/utils.pyc'>

导入的结果让我很吃惊,因为它显示第二个import语句已经覆盖了utils。然而文档指出 from statement will not bind a module name:

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.

事实上,当我在终端中使用 from ... import ... 语句时,没有引入模块名称:

>>> from os.path import abspath
>>> path
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'path' is not defined

我怀疑这与 Python 有关,在第二个导入语句时,尝试导入 testapp.api.utils 引用 testapp.utils 但失败了,但我没有确定。

这里发生了什么?

来自import system documentation:

When a submodule is loaded using any mechanism (e.g. importlib APIs, the import or import-from statements, or built-in __import__()) a binding is placed in the parent module’s namespace to the submodule object. For example, if package spam has a submodule foo, after importing spam.foo, spam will have an attribute foo which is bound to the submodule. Let’s say you have the following directory structure:

spam/
    __init__.py
    foo.py
    bar.py

and spam/__init__.py has the following lines in it:

from .foo import Foo
from .bar import Bar

then executing the following puts a name binding to foo and bar in the spam module:

>>> import spam
>>> spam.foo
<module 'spam.foo' from '/tmp/imports/spam/foo.py'>
>>> spam.bar
<module 'spam.bar' from '/tmp/imports/spam/bar.py'>

Given Python’s familiar name binding rules this might seem surprising, but it’s actually a fundamental feature of the import system. The invariant holding is that if you have sys.modules['spam'] and sys.modules['spam.foo'] (as you would after the above import), the latter must appear as the foo attribute of the former.

如果您执行 from testapp.api.utils import x,import 语句不会将 utils 加载到本地名称空间中。但是,导入机制 utils 加载到 testapp.api 命名空间中,以使进一步的导入工作正常。碰巧在你的情况下, testapp.api 也是本地名称空间,所以你会感到惊讶。