在Bash脚本中导入未安装Python包未报错但__file__为None的原因
问题场景
我遇到了一种从未见过的异常情况:
我编写了一个包含以下命令的Bash脚本:
python -c "import foo; print(foo.__file__); import pdb; pdb.set_trace()"
直接运行该Bash脚本时,foo.__file__的值为None,且该包指向一个_frozen_importlib_external._NamespaceLoader object:
None --Return-- > <string>(1)<module>()->None (Pdb) foo <module 'foo' (<_frozen_importlib_external._NamespaceLoader object at 0x7fbf30923790>)> (Pdb) foo.__file__
但直接在终端中运行这条命令时,却得到如下结果:
Traceback (most recent call last): File "<string>", line 1, in <module> ModuleNotFoundError: No module named 'foo'
是什么原因导致了这种不一致?我期望得到终端中的输出,即报告ModuleNotFoundError,而非静默生成__file__属性为None的模块。
原因分析
这种不一致的核心原因是Bash脚本运行时的当前工作目录下,存在一个名为foo的空目录(或无__init__.py文件的目录),而直接在终端运行命令时,当前目录不存在这个目录,或者终端的PYTHONPATH环境变量与脚本执行时存在差异。
Python 3.3及以上版本支持**命名空间包(Namespace Packages)**机制:当尝试导入某个模块名时,如果找不到对应.py文件或带__init__.py的常规包,但存在同名的无__init__.py目录,Python会将该目录识别为命名空间包,此时导入不会抛出ModuleNotFoundError,而是生成一个以_frozen_importlib_external._NamespaceLoader为加载器的模块对象,这类模块的__file__属性默认就是None。
而直接在终端运行时,当前工作目录没有这个foo目录,Python无法匹配到任何对应模块,就会正常抛出ModuleNotFoundError。
验证与解决方法
- 验证步骤:检查脚本执行时的工作目录,确认是否存在无
__init__.py的foo目录。 - 解决方式:
- 若不需要该命名空间包,直接删除这个
foo目录; - 若要将其转为常规Python包,在
foo目录中添加__init__.py文件; - 修改脚本的工作目录,避免Python加载到该命名空间包。
- 若不需要该命名空间包,直接删除这个
内容的提问来源于stack exchange,提问作者zkytony

