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

Python sub-sub-module: "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'

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

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

在试图回答这个问题时,我偶然发现了来自 Python 2.0 Reference Manual:

的这段文字

To avoid confusion, you cannot import sub-modules 'as' a different local name. So 'import module as m' is legal, but 'import module.submod as s' is not. The latter should be written as 'from module import submod as s', see below.

这与我收到的错误一致。但是,Python 2.7 Reference Manual 中的任何地方都没有这个(看似重要的)小广告。 Python 2.0 参考中的小提要是否仍然适用于 Python 2.7?或者我收到这个错误是出于我不知道的完全不同的原因?

替换sub2的代码__init__.py代码如下

sub/sub2/__init__.py:

from .square import square_it
def run():
    square_it(3)

我认为你偶然发现了一个鲜为人知的 Python "gotcha" 似乎是 fixed in Python 3.7 Alpha 1 thanks to Serhiy Storchaka. Check this question: .

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

The root cause is the import cycle. I played around with dis and found the following (I suspect others have already found this but the thread was hard to follow for me initially):

>>> 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
>>>

compared to

>>> 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
>>>

What this shows is that the implementation of "import a.b" and "import a.b as c" are different. The former calls import('a.b', ...) which returns the module 'a' and stores that in the variable 'a'. In the OP's case, because of the import cycle, while sys.modules['a.b'] exists, module 'a' does not yet have the attribute 'b'. That's the reason that in the latter example, the LOAD_ATTR opcode fails.

>>> 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

For "import sys.path as path", the given module name is "sys.path", and the "from list" entry on the stack is None. This fails before it even reaches the LOAD_ATTR line, since "sys.path" isn't an importable module. Changing LOAD_ATTR to IMPORT_FROM would thus have no effect on its behaviour.

>>> 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

For "from sys import path", the given module name is "sys", and the "from list" entry on the stack is a tuple containing the string "path". This works, since "sys" is importable and it has a "path" attribute.

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