假设我的项目结构如下:
proj
├── __init__.py
└── core
├── __init__.py
└── a
├── __init__.py
└── foo.py
其中唯一的非空文件是:
# proj/__init__.py
from . core import a
和
# proj/core/a/__init__.py
from . foo import Bar
和
# proj/core/a/foo.py
class Bar:
pass
这个想法是,我将模块a
从core
中取出,并将其暴露在包的顶层。但是,以下操作失败:
>>> from proj.a import Bar
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'proj.a'
与之一样:
>>> import proj.a.Bar as Bar
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'proj.a'
但是,如果使用完全限定的模块路径,则可以进行以下操作:
>>> from proj.core.a import Bar
两个问题:
python文档中是否有某些部分说明了为什么导入/模块系统不允许出现这种情况?我没有找到明确的解释。
是否有变通办法,以便您可以像从proj.a
一样从proj.core.a
导入?
答案 0 :(得分:1)
首先让我们解决一个简单的问题:import proj.a.Bar as Bar
无法工作,因为Bar
是一个类,而不是一个模块。由于同样的原因,import proj.core.a.Bar as Bar
也不会起作用。
现在可以从proj.a
进行导入。您使用__init__.py
文件描述的“提升”过程仅创建了一个别名-在proj.__dict__
中添加的条目“ a”现在已绑定到模块对象,但是它本身并不能使模块proj.core.a
成为proj
的直接子模块。
名称“ a”仅绑定在两个不同的命名空间中:proj.__dict__
和proj.core.__dict__
中。导入语句from proj.a import Bar
的处理方式仍与往常相同,试图查找名称为proj.a
的模块。 Python将从sys.meta_path
和sys.path_hooks
中选择导入机制(它将使用PathFinder
和FileFinder
),然后在sys.path
中搜索目录/子目录像proj/a
,找不到一个。
- python文档中是否有某些部分说明了为什么导入/模块系统不允许出现这种情况?我没有找到明确的解释。
我能找到的最好的方法是在导入系统文档5.3. Searching的这一部分中,其中提到 Python需要导入的模块的完全限定名称。不使用fully qualified name,而是使用其他名称。
- 有没有解决方法,以便您可以像从
proj.a
一样从proj.core.a
导入?
是的。在Python尝试导入模块之前,它首先检查该密钥是否已经在sys.modules
字典中缓存。尝试以下解决方法:
# proj/__init__.py
from . core import a
import sys
sys.modules[f"{__name__}.a"] = a
现在from proj.a import Bar
可以成功,并且即使proj
尚未导入也可以。
您正在劫持importlib here,因此 Python会主动启用此技巧。甚至还有a test case for it working in CPython,因为stdlib xml和etree也依赖于该行为。尽管源代码注释的确将其称为“ Crazy”,但请谨慎使用!