Spyder下Python导入模块触发ImportDenier无find_spec属性报错
模块导入异常问题说明
项目基础结构
my_module |-> _sub_module | |-> test_fun.py |-> test_fun1.py |-> run.py |-> __init__.py
各文件基础内容:
test_fun.py:
def test_fun(): pass
test_fun1.py:
def test_fun1(): pass
- 初始状态
run.py内容:
from my_module.test_fun1 import test_fun1 from my_module._sub_module.test_fun import test_fun
- 根目录
__init__.py为空文件
基础报错现象
当前复现环境为Anaconda安装的Spyder,配套Python 3.8.5版本。
正常运行场景:从my_module的父目录执行run.py,全量导入的情况下脚本可正常执行。
触发报错的条件:注释掉run.py中第一行导入语句,仅保留from my_module._sub_module.test_fun import test_fun,使用Spyder的runfile功能执行时触发异常,完整堆栈如下:
runfile('C:/Users/###/Documents/GitLab/test_project/my_module/run.py') Reloaded modules: my_module, my_module.test_fun1, my_module._sub_module.test_fun Traceback (most recent call last): File "<frozen importlib._bootstrap>", line 908, in _find_spec AttributeError: 'ImportDenier' object has no attribute 'find_spec' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "C:\Users\###\Documents\GitLab\test_project\my_module\run.py", line 4, in <module> from my_module._sub_module.test_fun import test_fun File "<frozen importlib._bootstrap>", line 991, in _find_and_load File "<frozen importlib._bootstrap>", line 971, in _find_and_load_unlocked File "<frozen importlib._bootstrap>", line 910, in _find_spec File "<frozen importlib._bootstrap>", line 884, in _find_spec_legacy File "C:\Users\###\Anaconda3\lib\site-packages\IPython\external\qt_loaders.py", line 48, in find_module if path: File "<frozen importlib._bootstrap_external>", line 1183, in __len__ File "<frozen importlib._bootstrap_external>", line 1162, in _recalculate File "<frozen importlib._bootstrap_external>", line 1158, in _get_parent_path KeyError: 'my_module'
核心待解答问题:
- 该报错的具体触发逻辑是什么
- 为什么导入内容完全无关、仅共享命名空间前缀的第一行导入语句,可完全规避报错
结构调整后的复现验证
将run.py移到my_module的上级目录(即test_project路径下)后,复现逻辑发生变化:第一次执行仅保留第二行导入的run.py时脚本可正常运行,但连续第二次执行相同脚本时,就会复现相同的ImportDenier报错,对应堆栈如下:
runfile('C:/Users/###/Documents/GitLab/test_project/run.py', wdir='C:/Users/###/Documents/GitLab/test_project') runfile('C:/Users/###/Documents/GitLab/test_project/run.py', wdir='C:/Users/###/Documents/GitLab/test_project') Reloaded modules: my_module, my_module._sub_module.test_fun Traceback (most recent call last): File "<frozen importlib._bootstrap>", line 908, in _find_spec AttributeError: 'ImportDenier' object has no attribute 'find_spec' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "C:\Users\###\Documents\GitLab\test_project\run.py", line 4, in <module> from my_module._sub_module.test_fun import test_fun File "<frozen importlib._bootstrap>", line 991, in _find_and_load File "<frozen importlib._bootstrap>", line 971, in _find_and_load_unlocked File "<frozen importlib._bootstrap>", line 910, in _find_spec File "<frozen importlib._bootstrap>", line 884, in _find_spec_legacy File "C:\Users\###\Anaconda3\lib\site-packages\IPython\external\qt_loaders.py", line 48, in find_module if path: File "<frozen importlib._bootstrap_external>", line 1183, in __len__ File "<frozen importlib._bootstrap_external>", line 1162, in _recalculate File "<frozen importlib._bootstrap_external>", line 1158, in _get_parent_path KeyError: 'my_module'
交叉验证结果:
- 直接在系统普通命令行、独立IPython控制台运行该脚本时,无法复现上述报错
- 问题根源与Spyder的内置执行逻辑直接相关
报错根因拆解
- 堆栈中的核心异常触发源来自IPython内置的
qt_loaders.py模块:其中定义的ImportDenier导入钩子原本用于拦截Qt相关模块的重复导入,仅实现了Python 3.3之前旧版导入协议要求的find_module方法,没有实现新版导入协议要求的find_spec方法。 - Spyder的
runfile执行逻辑默认开启用户模块自动重载机制:每次执行脚本前,会将上下文已加载的自定义模块做卸载处理,把对应模块从sys.modules字典中移除,但不会同步更新路径解析器内存储的父模块路径缓存。 - 当脚本直接导入下划线开头的私有子模块(即
_sub_module)时,导入流程不会提前完成父包my_module的初始化:导入器在遍历所有注册的导入钩子做路径校验时,会优先尝试调用ImportDenier的find_spec方法,因方法缺失触发异常,流程自动 fallback 到旧版的find_module分支,此时尝试读取已经被从sys.modules中移除的my_module的路径属性做长度判断,就会触发路径解析的KeyError。 - 第一行导入能规避报错的逻辑:
from my_module.test_fun1 import test_fun1会在流程最开始完成my_module根包的全量加载,将根包的路径信息同步写入sys.modules缓存,即使后续模块重载流程做了缓存清理,根包的路径解析链路已经提前建立,不会触发ImportDenier钩子的旧版逻辑校验分支。
临时规避方案
- 调整导入顺序,先导入根包下任意非下划线开头的公开子模块,再导入私有子模块
- 在Spyder的
工具>偏好设置>Python解释器中,关闭用户模块自动重载选项,避免每次执行前清空导入缓存 - 升级IPython和Spyder到最新版本,新版本已为
ImportDenier补充find_spec方法的实现,从根源上避免旧版导入协议的逻辑冲突
内容的提问来源于stack exchange,提问作者Luca
相关产品推荐
相关产品推荐

