从__未来__进口到绝对_进口究竟做了什么?

我有一个关于Python中绝对导入的回答了问题,在阅读Python 2.5更新日志和附带的佩普的基础上,我认为我理解了这个问题。然而,在安装Python2.5并尝试创建一个正确使用from __future__ import absolute_import的示例时,我意识到事情并不是那么清楚。

直接从上面链接的ChangeLog中,这句话准确地总结了我对绝对进口变化的理解:

假设您有一个这样的软件包目录:

pkg/
pkg/__init__.py
pkg/main.py
pkg/string.py

这定义了名为pkg的包,其中包含pkg.mainpkg.string子模块。

考虑main.py模块中的代码。如果它执行ABC0__语句,会发生什么情况?在Python2.4及更早版本中,它将首先查找包的目录以执行相对导入,找到PKG/string.py,将该文件的内容导入为pkg.string模块,并且该模块被绑定到pkg.main模块的命名空间中的名称"string"

所以我创建了这个精确的目录结构:

$ ls -R
.:
pkg/


./pkg:
__init__.py  main.py  string.py

__init__.pystring.py为空。main.py包含以下代码:

import string
print string.ascii_uppercase

正如预期的那样,在Python2.5中运行它会失败,并出现__abc0:

$ python2.5 pkg/main.py
Traceback (most recent call last):
File "pkg/main.py", line 2, in <module>
print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'

然而,在2.5 ChangeLog中,我们进一步发现了以下内容(着重号另加):

在Python2.5中,您可以使用from __future__ import absolute_import指令将import的行为切换为绝对导入。这种绝对导入行为将成为未来版本(可能是Python2.7)的默认设置。一旦绝对导入成为默认设置,import string将始终查找标准库的版本。

因此,我创建了pkg/main2.py,与main.py相同,但具有额外的未来进口指令。现在看起来是这样的:

from __future__ import absolute_import
import string
print string.ascii_uppercase

然而,用Python2.5运行它..失败,出现AttributeError

$ python2.5 pkg/main2.py
Traceback (most recent call last):
File "pkg/main2.py", line 3, in <module>
print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'

这与import string总是找到启用了绝对导入的STD-lib版本的说法完全矛盾。更重要的是,尽管有绝对导入将成为“新的默认”行为的警告,但我在使用Python2.7(使用或不使用__future__指令)时都遇到了同样的问题:

$ python2.7 pkg/main.py
Traceback (most recent call last):
File "pkg/main.py", line 2, in <module>
print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'


$ python2.7 pkg/main2.py
Traceback (most recent call last):
File "pkg/main2.py", line 3, in <module>
print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'

以及Python 3.5,使用或不使用(假设在两个文件中更改了print语句):

$ python3.5 pkg/main.py
Traceback (most recent call last):
File "pkg/main.py", line 2, in <module>
print(string.ascii_uppercase)
AttributeError: module 'string' has no attribute 'ascii_uppercase'


$ python3.5 pkg/main2.py
Traceback (most recent call last):
File "pkg/main2.py", line 3, in <module>
print(string.ascii_uppercase)
AttributeError: module 'string' has no attribute 'ascii_uppercase'

我已经测试了它的其他变体。我没有创建__abc0,而是创建了一个空模块--一个名为__abc1的目录,其中只包含一个空__abc2--并且我没有从__abc3发出导入,而是将abc4__到__abc5,并直接从repl运行导入。这些变化(或它们的组合)都不会改变上述结果。我无法将此与我所读到的有关__future__指令和绝对导入的内容相协调。

在我看来,这很容易用以下内容来解释(这来自Python 2文档,但这条语句在Python 3的相同文档中保持不变):

系统路径

(...)

正如程序启动时初始化的一样,该列表的第一项(__abc0)是包含用于调用Python解释器的脚本的目录。如果脚本目录不可用(例如,如果以交互方式调用解释器或如果从标准输入读取脚本),则path[0]是空字符串,它指示Python首先搜索当前目录中的模块。

那我错过了什么?为什么__future__语句似乎并不像它所说的那样,文档的这两个部分之间以及描述的行为和实际行为之间的矛盾是如何解决的?

127724 次浏览

The difference between absolute and relative imports come into play only when you import a module from a package and that module imports an other submodule from that package. See the difference:

$ mkdir pkg
$ touch pkg/__init__.py
$ touch pkg/string.py
$ echo 'import string;print(string.ascii_uppercase)' > pkg/main1.py
$ python2
Python 2.7.9 (default, Dec 13 2014, 18:02:08) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pkg.main1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "pkg/main1.py", line 1, in <module>
import string;print(string.ascii_uppercase)
AttributeError: 'module' object has no attribute 'ascii_uppercase'
>>>
$ echo 'from __future__ import absolute_import;import string;print(string.ascii_uppercase)' > pkg/main2.py
$ python2
Python 2.7.9 (default, Dec 13 2014, 18:02:08) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pkg.main2
ABCDEFGHIJKLMNOPQRSTUVWXYZ
>>>

In particular:

$ python2 pkg/main2.py
Traceback (most recent call last):
File "pkg/main2.py", line 1, in <module>
from __future__ import absolute_import;import string;print(string.ascii_uppercase)
AttributeError: 'module' object has no attribute 'ascii_uppercase'
$ python2
Python 2.7.9 (default, Dec 13 2014, 18:02:08) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pkg.main2
ABCDEFGHIJKLMNOPQRSTUVWXYZ
>>>
$ python2 -m pkg.main2
ABCDEFGHIJKLMNOPQRSTUVWXYZ

Note that python2 pkg/main2.py has a different behaviour then launching python2 and then importing pkg.main2 (which is equivalent to using the -m switch).

If you ever want to run a submodule of a package always use the -m switch which prevents the interpreter for chaining the sys.path list and correctly handles the semantics of the submodule.

Also, I much prefer using explicit relative imports for package submodules since they provide more semantics and better error messages in case of failure.

The changelog is sloppily worded. from __future__ import absolute_import does not care about whether something is part of the standard library, and import string will not always give you the standard-library module with absolute imports on.

from __future__ import absolute_import means that if you import string, Python will always look for a top-level string module, rather than current_package.string. However, it does not affect the logic Python uses to decide what file is the string module. When you do

python pkg/script.py

pkg/script.py doesn't look like part of a package to Python. Following the normal procedures, the pkg directory is added to the path, and all .py files in the pkg directory look like top-level modules. import string finds pkg/string.py not because it's doing a relative import, but because pkg/string.py appears to be the top-level module string. The fact that this isn't the standard-library string module doesn't come up.

To run the file as part of the pkg package, you could do

python -m pkg.script

In this case, the pkg directory will not be added to the path. However, the current directory will be added to the path.

You can also add some boilerplate to pkg/script.py to make Python treat it as part of the pkg package even when run as a file:

if __name__ == '__main__' and __package__ is None:
__package__ = 'pkg'

However, this won't affect sys.path. You'll need some additional handling to remove the pkg directory from the path, and if pkg's parent directory isn't on the path, you'll need to stick that on the path too.