尝试从扁平命名空间导入模块时出现 ModuleNotFoundError
ModuleNotFoundError when attempting to import module from flattened namespace
假设我的项目结构如下:
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 文档中是否有某些部分解释了为什么 import/module 系统不允许失败案例?我没能找到明确的解释。
是否有变通办法让您可以像从 proj.core.a
一样从 proj.a
导入?
首先让我们先解决一个简单的问题:import proj.a.Bar as Bar
无法工作,因为 Bar
是一个 class,而不是一个模块。出于同样的原因,import proj.core.a.Bar as Bar
也不会奏效。
现在从 proj.a
导入。您描述的使用 __init__.py
文件的 "lifting" 进程只创建了一个别名 - proj.__dict__
中添加的条目 "a" 现在绑定到一个模块对象,但它本身确实not make 不会使模块 proj.core.a
成为 proj
的直接子模块。
名称 "a" 刚刚绑定在两个不同的 命名空间 中:在 proj.__dict__
和 proj.core.__dict__
中。 import 语句 from proj.a import Bar
仍然以与往常相同的方式处理,试图找到具有完全限定名称 proj.a
的模块。 Python 将 select 从 sys.meta_path
and sys.path_hooks
(it will use a PathFinder
and a FileFinder
), and then search through sys.path
进口机器用于 dir/subdir 像 proj/a
,找不到。
- Is there some section of the python documentation that explains why the import/module system doesn't allow the failing cases? I haven't been able to find a clear explanation.
我能找到的最好的是在导入系统文档的这一部分 5.3. Searching, where it's mentioned that Python needs the fully qualified name of the module being imported. The failing case doesn't use the fully qualified name,它使用了其他名称。
- Is there a work around so that you can import from
proj.a
as if it were 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, so Python actively enables this trick. There is even a test case for it working in CPython,因为 stdlib xml 和 etree 也依赖于该行为。尽管源代码注释确实称其为 "Crazy",因此请谨慎使用!
假设我的项目结构如下:
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 文档中是否有某些部分解释了为什么 import/module 系统不允许失败案例?我没能找到明确的解释。
是否有变通办法让您可以像从
proj.core.a
一样从proj.a
导入?
首先让我们先解决一个简单的问题:import proj.a.Bar as Bar
无法工作,因为 Bar
是一个 class,而不是一个模块。出于同样的原因,import proj.core.a.Bar as Bar
也不会奏效。
现在从 proj.a
导入。您描述的使用 __init__.py
文件的 "lifting" 进程只创建了一个别名 - proj.__dict__
中添加的条目 "a" 现在绑定到一个模块对象,但它本身确实not make 不会使模块 proj.core.a
成为 proj
的直接子模块。
名称 "a" 刚刚绑定在两个不同的 命名空间 中:在 proj.__dict__
和 proj.core.__dict__
中。 import 语句 from proj.a import Bar
仍然以与往常相同的方式处理,试图找到具有完全限定名称 proj.a
的模块。 Python 将 select 从 sys.meta_path
and sys.path_hooks
(it will use a PathFinder
and a FileFinder
), and then search through sys.path
进口机器用于 dir/subdir 像 proj/a
,找不到。
- Is there some section of the python documentation that explains why the import/module system doesn't allow the failing cases? I haven't been able to find a clear explanation.
我能找到的最好的是在导入系统文档的这一部分 5.3. Searching, where it's mentioned that Python needs the fully qualified name of the module being imported. The failing case doesn't use the fully qualified name,它使用了其他名称。
- Is there a work around so that you can import from
proj.a
as if it wereproj.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, so Python actively enables this trick. There is even a test case for it working in CPython,因为 stdlib xml 和 etree 也依赖于该行为。尽管源代码注释确实称其为 "Crazy",因此请谨慎使用!