__future__导入absolute_import实际上做了什么?

时间:2015-11-16 20:18:12

标签: python python-2.7 python-import python-2.5

我有answered关于Python中绝对导入的问题,我认为我基于阅读the Python 2.5 changelog并附带PEP来理解。但是,在安装Python 2.5并尝试正确使用from __future__ import absolute_import的示例时,我意识到事情并不那么明确。

直接从上面链接的更改日志中,此语句准确地总结了我对绝对导入更改的理解:

  

假设你有一个像这样的包目录:

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

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

     

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

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

$ ls -R
.:
pkg/

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

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

import string
print string.ascii_uppercase

正如预期的那样,使用Python 2.5运行它会失败并显示AttributeError

$ 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更新日志中,我们发现了这一点(强调添加):

  

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

因此,我创建了pkg/main2.py,与main.py相同,但附加了将来的import指令。它现在看起来像这样:

from __future__ import absolute_import
import string
print string.ascii_uppercase

使用Python 2.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版本的说法相矛盾。更重要的是,尽管警告绝对进口计划成为新的默认进口&#34;行为,我使用Python 2.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'

我测试了其他变体。而不是string.py,我创建了一个空模块 - 一个名为string的目录,只包含一个空__init__.py - 而不是从main.py发出导入,我有{ {1}}&#39; d到cd并直接从REPL运行导入。这些变化(也没有它们的组合)都没有改变上面的结果。我无法将此与我所读到的有关pkg指令和绝对导入的内容相协调。

在我看来,这可以通过the following轻松解释(这是来自Python 2文档,但这个语句在Python 3的相同文档中保持不变):

  

sys.path中

     

(...)

     

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

那我错过了什么?为什么path[0]陈述看似不符合它的说法,这两个文档部分之间以及描述和实际行为之间的这种矛盾的解决方案是什么?

2 个答案:

答案 0 :(得分:73)

更改日志措辞粗糙。 from __future__ import absolute_import并不关心某些内容是否属于标准库,而import string并不总是为您提供带有绝对导入的标准库模块。

from __future__ import absolute_import表示如果您import string,Python将始终寻找顶级string模块,而不是current_package.string。但是,它不会影响Python用于确定string模块的文件的逻辑。当你这样做

python pkg/script.py

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

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

python -m pkg.script

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

您还可以向pkg/script.py添加一些样板,以使Python将其视为pkg包的一部分,即使作为文件运行时也是如此:

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

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

答案 1 :(得分:34)

绝对和相对导入之间的区别仅在从包导入模块并且该模块从该包导入其他子模块时才起作用。看到差异:

$ 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
>>> 

特别是:

$ 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列表并正确处理子模块的语义。

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