__future__ import absolute_import实际上做了什么?
我已经回答了一个有关Python中绝对导入的问题,我认为我是通过阅读Python 2.5更新日志和附带的PEP来理解的。 然而,在安装Python 2.5并尝试制作一个正确使用from __future__ import absolute_import
的例子,我意识到事情并不那么清楚。
直接从上面链接的changelog,这个陈述准确地总结了我对绝对导入变化的理解:
假设你有这样的软件包目录:
pkg/ pkg/__init__.py pkg/main.py pkg/string.py
这定义了一个名为
pkg
的包,其中包含pkg.main
和pkg.string
子模块。考虑main.py模块中的代码。 如果执行语句
import string
会发生什么? 在Python 2.4及更早的版本中,它将首先在包的目录中查找执行相对导入,findpkg / string.py,将该文件的内容作为pkg.string
模块pkg.string
,并将该模块绑定到名称"string"
在pkg.main
模块的名字空间中。
所以我创build了这个确切的目录结构:
$ ls -R .: pkg/ ./pkg: __init__.py main.py string.py
__init__.py
和string.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
的行为切换到绝对from __future__ import absolute_import
。 这个绝对导入行为将成为未来版本的默认值(可能是Python 2.7)。 一旦绝对导入是默认import string
将始终find标准库的版本。
因此,我创build了pkg/main2.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
将始终find启用绝对导入的std-lib版本的声明。 更重要的是,尽pipe绝对导入计划成为“新的默认”行为的警告,但我同时使用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'
我已经testing了这个的其他变化。 而不是string.py
,我创build了一个空模块 – 一个名为string
的目录,只包含一个空的__init__.py
– 而不是从main.py
发行导入,我有cd
到main.py
并直接从REPL。 这些变化(或它们的组合)都没有改变上述结果。 我无法调和这与我已经读了关于__future__
指令和绝对导入。
在我看来,这很容易通过以下来解释(这是来自Python 2文档,但是这个声明在Python 3的相同文档中保持不变):
sys.path中
(……)
在程序启动时初始化,这个列表中的第一个项目
path[0]
是包含用于调用Python解释器的脚本的目录。 如果脚本目录不可用(例如,如果解释器是交互式调用的,或者脚本是从标准input中读取的),则path[0]
是空string, 它指示Python首先search当前目录中的模块。
那么我错过了什么? 为什么__future__
陈述看起来不像它所说的那样,这两部分文献之间以及被描述和实际行为之间矛盾的解决是什么?
更新日志措辞粗俗。 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
目录被添加到path中,并且pkg
目录中的所有.py
文件看起来像顶级模块。 import string
findpkg/string.py
不是因为它正在执行相对导入,而是因为pkg/string.py
看起来是顶级模块string
。 事实上,这不是标准库string
模块没有出现。
要运行该文件作为pkg
包的一部分,你可以这样做
python -m pkg.script
在这种情况下, pkg
目录将不会被添加到path中。 但是,当前目录将被添加到path中。
你也可以在pkg/script.py
添加一些样板文件,以使Python作为pkg
包的一部分,即使在作为文件运行时也是如此:
if __name__ == '__main__' and __package__ is None: __package__ = 'pkg'
但是,这不会影响sys.path
。 您需要一些额外的处理来从path中删除pkg
目录,如果pkg
的父目录不在path上,则还需要将其粘贴到path上。
绝对导入和相对导入之间的区别仅在从包中导入模块并且该模块从该包导入其他子模块时才起作用。 看到不同:
$ 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
列表并正确处理子模块的语义。
此外,我更喜欢使用显式的相对导入包子模块,因为它们提供了更多的语义和更好的错误信息,以防止失败。