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

Two*_*ist 139 python python-2.5 python-import python-2.7

我已经回答了一个关于Python绝对导入的问题,我认为我在阅读Python 2.5更新日志和随附的PEP时理解了这一点.但是,在安装Python 2.5并尝试制作正确使用的示例时from __future__ import absolute_import,我意识到事情并不那么清楚.

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

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

pkg/
pkg/__init__.py
pkg/main.py
pkg/string.py
Run Code Online (Sandbox Code Playgroud)

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

考虑main.py模块中的代码.如果它执行语句会发生什么import string?在Python 2.4和更早的版本,它会先在包的目录进行相对进口看,发现包装/ string.py,导入该文件的内容pkg.string模块,并且该模块被绑定到名字"string"pkg.main模块的名称空间.

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

$ ls -R
.:
pkg/

./pkg:
__init__.py  main.py  string.py
Run Code Online (Sandbox Code Playgroud)

__init__.py并且string.py是空的.main.py包含以下代码:

import string
print string.ascii_uppercase
Run Code Online (Sandbox Code Playgroud)

正如所料,使用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'
Run Code Online (Sandbox Code Playgroud)

但是,在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
Run Code Online (Sandbox Code Playgroud)

然而,使用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'
Run Code Online (Sandbox Code Playgroud)

这与使用绝对导入import string始终会找到std-lib版本的语句完全相矛盾.更重要的是,尽管有绝对导入计划成为"新默认"行为的警告,但无论是否使用该__future__指令,我都使用Python 2.7来解决同样的问题:

$ 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'
Run Code Online (Sandbox Code Playgroud)

以及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'
Run Code Online (Sandbox Code Playgroud)

我测试了其他的变化.相反的string.py,我已经创建了一个空的模块-一个新的目录string仅包含一个空的__init__.py-而不是从发放的进口main.py,我cd倒是要pkg直接从REPL运行的进口.这些变化(也没有它们的组合)都没有改变上面的结果.我无法将此与我所读到的关于__future__指令和绝对导入的内容相协调.

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

sys.path中

(......)

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

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

use*_*ica 91

变更日志措辞粗俗.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
Run Code Online (Sandbox Code Playgroud)

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

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

python -m pkg.script
Run Code Online (Sandbox Code Playgroud)

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

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

if __name__ == '__main__' and __package__ is None:
    __package__ = 'pkg'
Run Code Online (Sandbox Code Playgroud)

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

  • 是的,就是我(为'没用'而投票',不是'错').从底部可以清楚地看出,OP了解`sys.path`是如何工作的,而实际的问题根本没有得到解决.也就是说,*__future__ import absolute_import`实际上做了什么?* (11认同)
  • @ Two-BitAlchemist:1)更改日志是松散的,非规范的.2)你停止遮蔽它.即使通过`sys.modules`进行搜索也不会得到标准库`string`模块,如果你用自己的顶层模块进行阴影处理.`from __future__ import absolute_import`并不意味着阻止顶层模块遮蔽顶层模块; 它应该阻止封装内部模块影响顶级模块.如果您将文件作为`pkg`包的一部分运行,则包的内部文件将停止显示为顶级文件. (4认同)
  • 好吧,我的意思是,我明白了.这正是我的帖子记录的行为.然而,面对这两个问题:(1.)如果"那不完全正确",为什么文档断然说它是?并且,(2)如果你不小心遮住了它,那么你如何"导入字符串",至少不要通过`sys.modules`掠过.这不是来自__future__ import absolute_import`的意图吗?它有什么作用?(PS,我不是下注者.) (2认同)
  • @storen:假设“pkg”是导入搜索路径上的包,则应该是“python -m pkg.main”。`-m` 需要模块名称,而不是文件路径。 (2认同)

Bak*_*riu 44

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

$ 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
>>> 
Run Code Online (Sandbox Code Playgroud)

特别是:

$ 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
Run Code Online (Sandbox Code Playgroud)

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

如果您想要运行包的子模块,请始终使用-m开关来阻止解释器查找sys.path列表并正确处理子模块的语义.

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