我已经回答了一个关于 Python中绝对导入的问题,我认为我根据阅读Python 2.5变更日志和随附的PEP理解了这个问题。然而,在安装 Python 2.5 并尝试制作一个正确使用的示例时from __future__ importabsolute_import,我意识到事情并不是那么清楚。
from __future__ importabsolute_import
直接从上面链接的更改日志中,该声明准确地总结了我对绝对导入更改的理解:
假设你有一个这样的包目录: pkg/ pkg/__init__.py pkg/main.py pkg/string.py 这定义了一个pkg包含pkg.main和pkg.string子模块的包。 考虑 main.py 模块中的代码。如果它执行语句会发生什么import string?在 Python 2.4 及更早版本中,它会首先查看包的目录以执行相对导入,找到 pkg/string.py,将该文件的内容作为pkg.string模块导入,并且该模块绑定到模块命名空间"string"中pkg.main的名称。
假设你有一个这样的包目录:
pkg/ pkg/__init__.py pkg/main.py pkg/string.py
这定义了一个pkg包含pkg.main和pkg.string子模块的包。
pkg
pkg.main
pkg.string
考虑 main.py 模块中的代码。如果它执行语句会发生什么import string?在 Python 2.4 及更早版本中,它会首先查看包的目录以执行相对导入,找到 pkg/string.py,将该文件的内容作为pkg.string模块导入,并且该模块绑定到模块命名空间"string"中pkg.main的名称。
import string
"string"
所以我创建了这个确切的目录结构:
$ ls -R .: pkg/ ./pkg: __init__.py main.py string.py
__init__.py并且string.py是空的。main.py包含以下代码:
__init__.py
string.py
main.py
import string print string.ascii_uppercase
正如预期的那样,使用 Python 2.5 运行它会失败,并显示AttributeError:
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将始终找到标准库的版本。
import
from __future__ import absolute_import
因此,我创建了pkg/main2.py,与附加的未来导入指令相同main.py但具有附加的未来导入指令。现在看起来像这样:
pkg/main2.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__指令:
__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两个文件中的语句都已更改):
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直接从 REPL 运行导入cd。pkg这些变化(或它们的组合)都没有改变上述结果。我无法将这与我所读到的有关__future__指令和绝对导入的内容相协调。
string
cd
在我看来,这很容易通过以下方式解释(这来自 Python 2 文档,但此语句在 Python 3 的相同文档中保持不变):
系统路径 (…) 在程序启动时初始化时,此列表的第一项,path[0],是包含用于调用 Python 解释器的脚本的目录。如果脚本目录不可用(例如,如果交互调用解释器或从标准输入读取脚本),path[0]则为空字符串, 它指示 Python 首先搜索当前目录中的模块。
(…)
在程序启动时初始化时,此列表的第一项,path[0],是包含用于调用 Python 解释器的脚本的目录。如果脚本目录不可用(例如,如果交互调用解释器或从标准输入读取脚本),path[0]则为空字符串, 它指示 Python 首先搜索当前目录中的模块。
path[0]
那么我错过了什么?为什么该__future__声明看起来不像它所说的那样,这两个文档部分之间以及描述和实际行为之间的这种矛盾的解决方案是什么?
变更日志措辞草率。from __future__ import absolute_import不关心某些东西是否是标准库的一部分,并且import string不会总是为您提供具有绝对导入的标准库模块。
from __future__ import absolute_import意味着如果你import string,Python 将总是寻找一个顶级string模块,而不是current_package.string. 但是,它不会影响 Python 用来决定哪个文件是string模块的逻辑。当你这样做
current_package.string
python pkg/script.py
pkg/script.py看起来不像 Python 包的一部分。按照正常程序,将pkg目录添加到路径中,目录.py中的所有文件pkg看起来像顶级模块。import string发现pkg/string.py不是因为它正在执行相对导入,而是因为pkg/string.py似乎是顶级模块string。这不是标准库string模块的事实并没有出现。
pkg/script.py
.py
pkg/string.py
要将文件作为pkg包的一部分运行,您可以执行
python -m pkg.script
在这种情况下,pkg目录不会被添加到路径中。但是,当前目录将被添加到路径中。
您还可以添加一些样板文件pkg/script.py以使 Python 将其视为pkg包的一部分,即使作为文件运行:
if __name__ == '__main__' and __package__ is None: __package__ = 'pkg'
但是,这不会影响sys.path. 您需要一些额外的处理才能pkg从路径中删除目录,如果pkg的父目录不在路径上,您也需要将其粘贴在路径上。
sys.path