Python 子模块:"import as" vs "import"



我在Python 2.7中工作。

这是我的文件夹结构。(temp已添加到此系统路径。)

temp
|
|--main.py
|
|--sub
|
|--__init__.py
|
|--sub2
|
|--__init__.py
|
|--square.py

文件内容如下所示。

main.py:
import sub.sub2 as sub2
sub2.run()
sub/__init__.py: empty
sub/sub2/__init__.py:
import sub.sub2.square as square
def run():
square.square_it(3)
sub/sub2/square.py:
def square_it(x): return x**2

当我执行main.py时,我收到以下错误(忽略行号):

Traceback (most recent call last):
File "main.py", line 3, in <module>
import sub.sub2 as sub2
File "/home/gimlisonofgloin1/temp/sub/sub2/__init__.py", line 3, in <module>
import sub.sub2.square as square
AttributeError: 'module' object has no attribute 'sub2'

我可以通过将发生错误的语句更改为以下任意语句来解决此问题(当然,在最后列出的三个解决方案中,我必须适当地更改函数调用):

  • from sub.sub2 import square as square
  • from sub.sub2.square import square_it
  • from .square import square_it(如用户NeErAj KuMaR的回答中善意指出的);或
  • import sub.sub2.square

我的问题是:为什么原始代码行会产生错误,尽管它在语义上等同于正在工作("固定")的代码行(特别是列出的第1和第4个解决方案)

在试图回答这个问题时,我偶然发现了Python 2.0参考手册中的这段文字:

为了避免混淆,不能将子模块"作为"其他本地名称导入。因此,"以m导入模块"是合法的,但"以s导入模块.submod"不是。后者应写成"从模块导入子模块as s",见下文。

这与我收到的错误一致。然而,这个(看似重要的)小简介并没有出现在Python 2.7参考手册的任何地方。Python2.0参考中的小简介在Python2.7中仍然适用吗?还是我犯这个错误的原因完全不同,而我并不知道?

我想你偶然发现了一个鲜为人知的Python"gotcha",多亏了Serhiy Storchaka,它似乎在Python 3.7 Alpha 1中得到了修复。检查此问题:http://stackoverflow.com/questions/41845671/import-as-in-python-3.

这在Python Ideas中进行了讨论,但import foo.bar as eggsfrom foo import bar as eggs不同。它们生成不一致的字节码。

根本原因是导入周期。我和dis玩了一玩,发现了以下内容(我怀疑其他人已经发现了,但最初我很难跟上):

>>> dis.dis('import a.b')
1         0 LOAD_CONST               0 (0)
2 LOAD_CONST               1 (None)
4 IMPORT_NAME              0 (a.b)
6 STORE_NAME               1 (a)
8 LOAD_CONST               1 (None)
10 RETURN_VALUE
>>>

与相比

>>> dis.dis('import a.b as c')
1         0 LOAD_CONST               0 (0)
2 LOAD_CONST               1 (None)
4 IMPORT_NAME              0 (a.b)
6 LOAD_ATTR                1 (b)      <-- error here
8 STORE_NAME               2 (c)
10 LOAD_CONST               1 (None)
12 RETURN_VALUE
>>>

这表明"导入a.b"one_answers"将a.b导入为c"的实现是不同的。前者调用import('a.b',…),后者返回模块"a"并将其存储在变量"a"中。在OP的情况下,由于导入周期的原因,虽然sys.modules[a.b']存在,但模块'a'还没有属性'b'。这就是后一个例子中LOAD_ATTR操作码失败的原因。

>>> dis("import sys.path as path")
1         0 LOAD_CONST               0 (0)
3 LOAD_CONST               1 (None)
6 IMPORT_NAME              0 (sys.path)
9 LOAD_ATTR                1 (path)
12 STORE_NAME               1 (path)
15 LOAD_CONST               1 (None)
18 RETURN_VALUE

对于"import sys.path as path",给定的模块名称为"sys.path",堆栈上的"from list"条目为None。这甚至在到达LOAD_ATTR行之前就失败了,因为"sys.path"不是一个可导入的模块。因此,将LOAD_ATTR更改为IMPORT_FROM对其行为没有影响。

>>> dis("from sys import path")
1         0 LOAD_CONST               0 (0)
3 LOAD_CONST               1 (('path',))
6 IMPORT_NAME              0 (sys)
9 IMPORT_FROM              1 (path)
12 STORE_NAME               1 (path)
15 POP_TOP
16 LOAD_CONST               2 (None)
19 RETURN_VALUE

对于"from sys import path",给定的模块名称为"sys",堆栈上的"from list"条目是一个包含字符串"path"的元组。这是有效的,因为"sys"是可导入的并且具有"path"属性。

我想你必须等待3.7或更改导入。

将sub2__init__.py代码的代码替换为以下

sub/sub2/__init__.py:
from .square import square_it
def run():
square_it(3)

最新更新