__init__.py 中的导入和 'import as' 语句

27 投票
4 回答
13036 浏览
提问于 2025-04-18 13:43

我遇到了一个问题,涉及到在 __init__.py 文件中使用导入,以及在包的模块中使用 import as 的绝对导入。

我的项目有一个子包,在它的 __init__.py 文件中,我用 from import as 语句把一个类“提升”到子包的层级。这个模块又用绝对导入从那个子包中导入其他模块。结果我遇到了这个错误:AttributeError: 'module' object has no attribute 'subpkg'

示例

结构:

pkg/
├── __init__.py
├── subpkg
│   ├── __init__.py
│   ├── one.py
│   └── two_longname.py
└── tst.py

pkg/init.py 是空的。

pkg/subpkg/init.py:

from pkg.subpkg.one import One

pkg/subpkg/one.py:

import pkg.subpkg.two_longname as two

class One(two.Two):
    pass

pkg/subpkg/two_longname.py:

class Two:
    pass

pkg/tst.py:

from pkg.subpkg import One

print(One)

输出:

$ python3.4 -m pkg.tst
Traceback (most recent call last):
  File "/usr/lib/python3.4/runpy.py", line 170, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/lib/python3.4/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/home/and/dev/test/python/imptest2/pkg/tst.py", line 1, in <module>
    from pkg.subpkg import One
  File "/home/and/dev/test/python/imptest2/pkg/subpkg/__init__.py", line 1, in <module>
    from pkg.subpkg.one import One
  File "/home/and/dev/test/python/imptest2/pkg/subpkg/one.py", line 1, in <module>
    import pkg.subpkg.two_longname as two
AttributeError: 'module' object has no attribute 'subpkg'

解决方法

有一些改动可以让它正常工作:

  1. pkg/subpkg/__init__.py 留空,直接从 pkg.subpkg.one 导入。

    我不认为这是一个选项,因为据我所知,把东西“提升”到包的层级是可以的。这里引用一篇文章中的话:

    在你的 __init__.py 中,常见的做法是将选定的类、函数等导入到包的层级,这样可以方便地从包中导入。

  2. one.py 中把 import as 改成 from import

     from pkg.subpkg import two_longname
    
     class One(two_longname.Two):
         pass
    

    这样做的唯一缺点是我不能为模块创建一个简短的别名。这个想法是从 @begueradj 的回答中得到的。

one.py 中也可以使用相对导入来解决这个问题。但我觉得这只是解决方法 #2 的一种变体。

问题

  1. 有人能解释一下这里到底发生了什么吗?为什么在 __init__.py 中的导入和使用 import as 的组合会导致这样的错误?

  2. 有没有更好的解决方法?


原始示例

这是我的原始示例。虽然不太现实,但我不想删掉它,这样 @begueradj 的回答仍然有意义。

pkg/init.py 是空的。

pkg/subpkg/init.py:

from pkg.subpkg.one import ONE

pkg/subpkg/one.py:

import pkg.subpkg.two
ONE = pkg.subpkg.two.TWO

pkg/subpkg/two.py:

TWO = 2

pkg/tst.py:

from pkg.subpkg import ONE

输出:

$ python3.4 -m pkg.tst
Traceback (most recent call last):
  File "/usr/lib/python3.4/runpy.py", line 170, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/lib/python3.4/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/home/and/dev/test/python/imptest/pkg/tst.py", line 1, in <module>
    from pkg.subpkg import ONE
  File "/home/and/dev/test/python/imptest/pkg/subpkg/__init__.py", line 2, in <module>
    from pkg.subpkg.one import ONE
  File "/home/and/dev/test/python/imptest/pkg/subpkg/one.py", line 6, in <module>
    ONE = pkg.subpkg.two.TWO
AttributeError: 'module' object has no attribute 'subpkg'

最开始我在 one.py 中有这个:

import pkg.subpkg.two as two
ONE = two.TWO

在这种情况下,我在导入时会遇到错误(就像我原来的项目一样,也使用了 import as)。

4 个回答

2

你的项目结构在调用模块时,应该像这样:

pkg/
├── __init__.py
├── subpkg
│   ├── __init__.py
│   ├── one.py
│   └── two.py
tst.py

把你的 two.py 定义成这样:

class TWO:
    def functionTwo(self):
        print("2")

把你的 one.py 定义成这样:

from pkg.subpkg import two
class ONE:
    def functionOne(self):
        print("1")

        self.T=two.TWO()
        print("Calling TWO from ONE: ")
        self.T.functionTwo()

把你的 test.py 定义成这样:

from pkg.subpkg import one
class TEST:
    def functionTest(self):
        O=one.ONE()
        O.functionOne()
if __name__=='__main__':
    T=TEST()
    T.functionTest()

当你执行时,你会得到这个结果:

1
Calling  TWO from  ONE:
2

希望这对你有帮助。

3

正如被接受的答案所说,这是Python的一种行为问题。

我已经提交了一个bug报告:http://bugs.python.org/issue30024

Serhiy Storchaka的修复已经合并,预计会在Python 3.7中推出。

3

这里有一个关于发生了什么的理论。

当你使用 as 这个保留字时,比如:

import pkg.subpkg.two_longname as two

Python 必须完全初始化并解决与 pkg.subpkg 相关的所有依赖关系。但是有个问题,要完全加载 subpkg,你也需要完全加载 one.py,对吧?而 one.py 又同时导入了 two_longname.py,而且是用 as 这个关键字……你能看到这里的递归吗?这就是为什么当你执行:

import pkg.subpkg.two_longname as two

时,会出现一个错误,提示 subpkg 不存在。

为了进行测试,去修改 one.py,把它改成这样:

#import pkg.subpkg.two_longname as two
from pkg.subpkg import two_longname

#class One(two.Two):
class One(two_longname.Two):
    pass

我想这都是为了性能,Python 在可能的情况下会部分加载一个模块。而 as 这个关键字是其中的一个例外。我不知道还有没有其他例外,但知道这些会很有趣。

21

你可能误以为在使用 from ... import 时不能有别名,其实从 Python 2.0 开始就有 from ... import ... as 这个用法。import ... as 是一种不太常见的语法,很多人不太了解,但你在代码中不小心用了它。

PEP 0221 声称以下两种写法“实际上”是一样的:

  1. import foo.bar.bazaar as baz
  2. from foo.bar import bazaar as baz

这个说法在Python 3.6.x 及之前的版本并不完全正确,因为你遇到的特殊情况就是一个例子:如果所需的模块已经在 sys.modules 中,但还没有初始化。import ... as 要求模块 foo.bar 必须在 foo 的命名空间中作为属性 bar 被引入,并且还要在 sys.modules 中,而 from ... import ... as 只是在 sys.modules 中查找 foo.bar

(另外要注意,import foo.bar 只是确保模块 foo.barsys.modules 中并且可以作为 foo.bar 访问,但可能还没有完全初始化。)

我通过如下修改代码解决了问题:

# import pkg.subpkg.two_longname as two
from pkg.subpkg import two_longname as two

而且这段代码在 Python 2 和 Python 3 上都能完美运行。

此外,在 one.py 中你不能写 from pkg import subpkg,原因是一样的。


为了进一步演示这个问题,按照上面的方式修复你的 one.py,并在 tst.py 中添加以下代码:

import pkg
import pkg.subpkg.two_longname as two

del pkg.subpkg

from pkg.subpkg import two_longname as two
import pkg.subpkg.two_longname as two

只有最后一行会崩溃,因为 from ... import 会在 sys.modules 中查找 pkg.subpkg 并找到它,而 import ... as 则在 sys.modules 中查找 pkg,并试图在 pkg 模块中找到 subpkg 作为一个属性。因为我们刚刚删除了那个属性,最后一行就会失败,报错 AttributeError: 'module' object has no attribute 'subpkg'


由于 import foo.bar as baz 这种语法有点晦涩,并且会引入更多特殊情况,我几乎没见过它被使用过,所以我建议完全避免使用它,优先选择 from .. import ... as

撰写回答