关于python:从__future__导入absolute_import实际上做了什么?

What does from __future__ import absolute_import actually do?

我回答了一个关于python中绝对导入的问题,我认为我是通过阅读python2.5的changelog和附带的pep理解的。然而,在安装了python 2.5并试图创建一个正确使用from __future__ import absolute_import的示例时,我意识到事情并不那么清楚。

直接从上面链接的changelog,这个语句准确地总结了我对绝对导入更改的理解:

Let's say you have a package directory like this:

1
2
3
4
pkg/
pkg/__init__.py
pkg/main.py
pkg/string.py

This defines a package named pkg containing the pkg.main and pkg.string submodules.

Consider the code in the main.py module. What happens if it executes the statement import string? In Python 2.4 and earlier, it will first look in the package's directory to perform a relative import, finds pkg/string.py, imports the contents of that file as the pkg.string module, and that module is bound to the name "string" in the pkg.main module's namespace.

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

1
2
3
4
5
6
$ ls -R
.:
pkg/

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

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

1
2
import string
print string.ascii_uppercase

正如预期的那样,使用python 2.5运行此命令会在使用AttributeError时失败:

1
2
3
4
5
$ 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的变更日志中,我们发现了这一点(重点增加):

In Python 2.5, you can switch import's behaviour to absolute imports using a from __future__ import absolute_import directive. This absolute-import behaviour will become the default in a future version (probably Python 2.7). Once absolute imports are the default, import string will always find the standard library's version.

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

1
2
3
from __future__ import absolute_import
import string
print string.ascii_uppercase

不过,用python 2.5运行这个程序…使用AttributeError失败:

1
2
3
4
5
$ 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版本的说法完全相反。更重要的是,尽管有警告称绝对导入计划成为"新的默认"行为,我还是使用python 2.7,使用或不使用__future__指令来解决这个问题:

1
2
3
4
5
6
7
8
9
10
11
$ 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语句都已更改):

1
2
3
4
5
6
7
8
9
10
11
$ 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'

我测试过其他的变化。我创建了一个空模块,名为string,它只包含一个空的__init__.py,而不是从main.py发出导入,而是将cd发送到pkg,并直接从repl运行导入。这些变化(或它们的组合)都没有改变上述结果。我不能将这与我读到的有关__future__指令和绝对进口的内容相协调。

在我看来,这很容易通过以下内容来解释(这是从python 2文档中得到的,但在python 3的同一个文档中,此语句保持不变):

sys.path

(...)

As initialized upon program startup, the first item of this list, path[0], is the directory containing the script that was used to invoke the Python interpreter. If the script directory is not available (e.g. if the interpreter is invoked interactively or if the script is read from standard input), path[0] is the empty string, which directs Python to search modules in the current directory first.

那我错过了什么?为什么__future__声明看起来不像它所说的那样,并且如何解决这两个文档部分之间以及所描述的和实际行为之间的矛盾?


这个变更日志措辞粗俗。from __future__ import absolute_import不关心某个东西是否是标准库的一部分,import string也不会总是给你绝对导入的标准库模块。

from __future__ import absolute_import意味着,如果您使用import string,python将始终查找顶级的string模块,而不是current_package.string。但是,它不会影响python用来决定哪个文件是string模块的逻辑。当你这样做的时候

1
python pkg/script.py

对于python来说,pkg/script.py看起来不像包的一部分。按照正常程序,将pkg目录添加到路径中,pkg目录中的所有.py文件看起来像顶级模块。import string发现pkg/string.py不是因为它正在进行相对导入,而是因为pkg/string.py似乎是顶级模块string。这不是标准库string模块的事实不会出现。

要将文件作为pkg包的一部分运行,可以

1
python -m pkg.script

在这种情况下,不会将pkg目录添加到路径中。但是,当前目录将添加到路径中。

您还可以在pkg/script.py中添加一些样板文件,使python将其视为pkg包的一部分,即使作为文件运行:

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

但是,这不会影响sys.path。您将需要一些额外的处理来从路径中删除pkg目录,如果pkg的父目录不在路径上,您也需要将其保留在路径上。


只有从包中导入模块,并且该模块从包中导入其他子模块时,绝对导入和相对导入之间的差异才会发挥作用。请看区别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ 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
>>>

特别地:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ 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

注意,python2 pkg/main2.py有不同的行为,然后启动python2,然后导入pkg.main2(相当于使用-m开关)。

如果要运行包的子模块,请始终使用-m开关,以防止解释器更改sys.path列表,并正确处理子模块的语义。

另外,我更喜欢对包子模块使用显式的相对导入,因为它们在失败时提供更多的语义和更好的错误消息。