__init__.py 中的导入和 'import as' 语句
我遇到了一个问题,涉及到在 __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'
解决方法
有一些改动可以让它正常工作:
把
pkg/subpkg/__init__.py
留空,直接从pkg.subpkg.one
导入。我不认为这是一个选项,因为据我所知,把东西“提升”到包的层级是可以的。这里引用一篇文章中的话:
在你的
__init__.py
中,常见的做法是将选定的类、函数等导入到包的层级,这样可以方便地从包中导入。在
one.py
中把import as
改成from import
:from pkg.subpkg import two_longname class One(two_longname.Two): pass
这样做的唯一缺点是我不能为模块创建一个简短的别名。这个想法是从 @begueradj 的回答中得到的。
在 one.py
中也可以使用相对导入来解决这个问题。但我觉得这只是解决方法 #2 的一种变体。
问题
有人能解释一下这里到底发生了什么吗?为什么在
__init__.py
中的导入和使用import as
的组合会导致这样的错误?有没有更好的解决方法?
原始示例
这是我的原始示例。虽然不太现实,但我不想删掉它,这样 @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 个回答
你的项目结构在调用模块时,应该像这样:
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
希望这对你有帮助。
正如被接受的答案所说,这是Python的一种行为问题。
我已经提交了一个bug报告:http://bugs.python.org/issue30024
Serhiy Storchaka的修复已经合并,预计会在Python 3.7中推出。
这里有一个关于发生了什么的理论。
当你使用 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
这个关键字是其中的一个例外。我不知道还有没有其他例外,但知道这些会很有趣。
你可能误以为在使用 from ... import
时不能有别名,其实从 Python 2.0 开始就有 from ... import ... as
这个用法。import ... as
是一种不太常见的语法,很多人不太了解,但你在代码中不小心用了它。
PEP 0221 声称以下两种写法“实际上”是一样的:
import foo.bar.bazaar as baz
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.bar
在 sys.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
。