尝试从展平的命名空间导入模块时出现ModuleNotFoundError

时间:2020-05-19 13:07:26

标签: python python-3.x python-import

假设我的项目结构如下:

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

这个想法是,我将模块acore中取出,并将其暴露在包的顶层。但是,以下操作失败:

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

两个问题:

  1. python文档中是否有某些部分说明了为什么导入/模块系统不允许出现这种情况?我没有找到明确的解释。

  2. 是否有变通办法,以便您可以像从proj.a一样从proj.core.a导入?

1 个答案:

答案 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_pathsys.path_hooks中选择导入机制(它将使用PathFinderFileFinder),然后在sys.path中搜索目录/子目录像proj/a,找不到一个。

  1. python文档中是否有某些部分说明了为什么导入/模块系统不允许出现这种情况?我没有找到明确的解释。

我能找到的最好的方法是在导入系统文档5.3. Searching的这一部分中,其中提到 Python需要导入的模块的完全限定名称。不使用fully qualified name,而是使用其他名称。

  1. 有没有解决方法,以便您可以像从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”,但请谨慎使用!