pkgutil.iter_modules() 找不到 namespace package 的子包,因为它只扫描含 __init__.py 的目录,而 namespace package 依据 pep 420 不设该文件;推荐改用 importlib.metadata.files() 或 importlib.util.find_spec()。

为什么 pkgutil.iter_modules() 找不到 namespace package 里的子包
因为 pkgutil.iter_modules() 只扫描有 __init__.py 的目录,而 namespace package 故意不放这个文件。它依赖 PEP 420 的动态发现机制,pkgutil 没有实现该协议。
常见错误现象:iter_modules() 返回空列表,但你确认路径下确实有子包;用 importlib.metadata.entry_points() 或直接 import 却能成功加载。
- 只适用于传统包(含
__init__.py),对 PEP 420 namespace package 无效 - 即使把 namespace package 的根目录传给
iter_modules(path=[...]),它仍会跳过所有无__init__.py的子目录 - 如果你在开发插件系统、动态加载扩展模块,误用这个函数会导致“模块存在却遍历不到”
替代方案:用 importlib.metadata.files() + 手动路径解析
Python 3.9+ 支持通过 importlib.metadata 获取已安装 distribution 的文件列表,再按命名规则提取子包路径——这是目前最可靠、兼容 namespace package 的方式。
使用场景:你想列出某个已安装的 namespace 包(如 sqlalchemy.dialects)下所有可用子模块,且不依赖硬编码路径或 import 语句。
立即学习“Python免费学习笔记(深入)”;
-
importlib.metadata.files("sqlalchemy")返回Traversable对象列表,可遍历其结构 - 过滤出以
"sqlalchemy/dialects/"开头的路径,再用.name提取目录名(即子包名) - 注意:必须确保该 namespace package 是通过
pip install安装的(即有对应 distribution),开发中直接sys.path追加的路径不会被识别
from importlib import metadata
from pathlib import Path
<p>dist = metadata.distribution("sqlalchemy")
for f in dist.files or []:
if str(f).startswith("sqlalchemy/dialects/") and f.is_dir():
print(f.name) # 输出 "postgresql", "mysql", "sqlite" 等开发期调试:用 importlib.util.find_spec() 验证子包是否存在
当不确定某个子包是否被 Python 正确识别为 namespace package 的一部分时,find_spec() 是最轻量、最准确的探测方式。
它绕过文件系统扫描,直接走 import machinery 的查找逻辑,和真实 import 行为一致。
- 返回
ModuleSpec表示可导入;返回None表示找不到(不是路径问题,是 namespace 未被正确注册) - 对
pkgutil失效的 case,find_spec("myns.subpkg")往往能返回有效结果 - 如果返回
None,检查sys.path中是否包含该 namespace 的父目录,且该目录下没有__init__.py(否则会被当作普通包)
容易被忽略的兼容性细节
namespace package 的行为在不同 Python 版本和安装方式下差异很大,尤其容易在 CI 或容器环境中出问题。
- Python pkg_resources 或显式
declare_namespace() - 用
pip install -e .安装的 namespace package,其路径可能不在sys.path根目录,而是嵌套在src/下——此时files()查不到,得改用find_spec()或手动解析sys.path - 某些打包工具(如 PyInstaller)会忽略 namespace package 的多路径特性,导致运行时子包丢失
真正麻烦的从来不是“怎么写”,而是“谁在什么时候、以什么方式把哪些路径加进了 sys.path”。










