Python pkgutil 的 namespace package 扩展

1次阅读

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

Python pkgutil 的 namespace package 扩展

为什么 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”。

text=ZqhQzanResources