【问题标题】:What does from __future__ import absolute_import actually do?from __future__ import absolute_import 实际上做了什么?
【发布时间】:2016-02-18 01:16:36
【问题描述】:

我有 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 模块,并且该模块绑定到名称 @987654333 @ 在 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 中,您可以使用 from __future__ import absolute_import 指令将 import 的行为切换为绝对导入。这种绝对导入行为将成为未来版本(可能是 Python 2.7)中的默认行为。 一旦绝对导入成为默认值,import string 将始终找到标准库的版本。

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

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 版本的说法完全矛盾。更重要的是,尽管警告说绝对导入将成为“新的默认”行为,但我在使用 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发出导入,我有cd'd到pkg并直接从 REPL 运行导入。这些变化(或它们的组合)都没有改变上述结果。我无法将这与我所读到的关于 __future__ 指令和绝对导入的内容相协调。

在我看来,the following 很容易解释这一点(这来自 Python 2 文档,但在 Python 3 的相同文档中此语句保持不变):

系统路径

(...)

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

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

【问题讨论】:

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


【解决方案1】:

变更日志措辞草率。 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 目录添加到路径中,pkg 目录中的所有.py 文件看起来像顶级模块。 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.)如果“这不完全正确”,为什么文档断然说它是?并且,(2.) 那么,如果你不小心遮住了它,你会如何import string,至少没有通过sys.modules。这不是from __future__ import absolute_import 想要防止的吗?它有什么作用? (PS,我不是反对者。)
  • 是的,那是我(反对“没用”,而不是“错误”)。从底部可以清楚地看出,OP 了解 sys.path 的工作原理,而实际问题根本没有得到解决。也就是说,from __future__ import absolute_import 实际上做了什么?
  • @Two-BitAlchemist: 1) 变更日志措辞松散且不规范。 2)你停止遮蔽它。如果您使用自己的顶级模块遮蔽它,即使通过sys.modules 也不会为您提供标准库string 模块。 from __future__ import absolute_import 并不是要阻止顶级模块遮蔽顶级模块;它应该阻止包内部模块隐藏顶级模块。如果您将该文件作为pkg 包的一部分运行,则该包的内部文件将不再显示为顶级文件。
  • @Two-BitAlchemist:答案已修改。这个版本更有帮助吗?
  • @storen:假设pkg 是导入搜索路径上的一个包,应该是python -m pkg.main-m 需要模块名称,而不是文件路径。
【解决方案2】:

仅当您从包中导入模块并且该模块从该包中导入另一个子模块时,绝对导入和相对导入之间的区别才会起作用。看看区别:

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

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

【讨论】:

  • 所以本质上它只适用于您避免“当前目录”问题的狭窄情况?这似乎是一个比 PEP 328 和 2.5 变更日志所描述的弱得多的实现。您认为文档不准确吗?
  • @Two-BitAlchemist 实际上 you 正在做的是“狭义”。您只启动一个要执行的 python 文件,但这可能会触发数百个导入。一个包的子模块根本不应该被执行,仅此而已。
  • 为什么python2 pkg/main2.py 的行为与启动 python2 然后导入 pkg.main2 不同?
  • @storen 那是因为相对导入的行为发生了变化。当您启动 pkg/main2.py python(版本 2)时,notpkg 视为一个包。在使用python2 -m pkg.main2 或导入它时考虑pkg 是一个包。
猜你喜欢
  • 2021-04-12
  • 2011-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-02
  • 2020-11-21
  • 2015-02-26
相关资源
最近更新 更多