为何__init__.py会破坏Python命名空间包?如何兼容二者?
原本支持命名空间包的目录结构如下:
├── pkg │ ├── module │ │ └── a.py │ └── pyproject.toml └── subfolder ├── pkg │ └── module │ └── b.py └── pyproject.toml
但在pkg/module/目录下添加__init__.py后(目录结构变为如下):
├── pkg │ ├── module │ │ ├── __init__.py │ │ └── a.py
执行from pkg.module.b import something这类命名空间包导入语句时会报错:
ModuleNotFoundError: No module named 'pkg.module.b'
问题
- 为何
__init__.py会破坏命名空间包的安装与导入? - 是否存在可行的解决方案,既能保留
pkg/module/__init__.py,又能让命名空间包正常工作?
附两个pyproject.toml文件内容
pkg/pyproject.toml
[build-system] build-backend = "setuptools.build_meta" requires = ["setuptools>=61"] [project] dependencies = [] name = "my-pkg" version = "0.1.0" [tool.setuptools.packages.find] include = ["pkg*"] where = [".."]
subfolder/pyproject.toml
[build-system] build-backend = "setuptools.build_meta" requires = ["setuptools>=61"] [project] dependencies = [] name = "my-pkg-extension" version = "0.1.0" [tool.setuptools.packages.find] include = ["pkg*"]
1. 为什么__init__.py会破坏命名空间包?
Python的隐式命名空间包(PEP 420)核心逻辑是:当一个目录没有__init__.py,且被多个分布包同时包含时,Python会自动将这些目录合并为一个命名空间包。
一旦在pkg/module/下添加__init__.py,这个目录就会被识别为常规Python包,而非命名空间包的一部分。安装my-pkg后,Python会优先从这个已安装的常规包路径加载pkg.module,不会再去查找其他分布包中同名的pkg/module目录。这就导致my-pkg-extension里的pkg/module/b.py无法被合并到已有的pkg.module中,最终出现找不到模块的错误。
从setuptools打包逻辑来看,包含__init__.py的目录会被当作完整包处理,安装时会优先加载该包内容,而非合并同路径的其他包资源。
2. 保留__init__.py同时让命名空间包工作的方案
有两种可靠的解决方式:
方案一:显式声明pkg_resources风格命名空间包
在两个包的pkg/目录下都添加__init__.py,内容如下:
__import__('pkg_resources').declare_namespace(__name__)
同时在两个pyproject.toml的[project]区块中添加命名空间声明:
namespace_packages = ["pkg", "pkg.module"]
这是PEP 420之前的命名空间包实现方式,setuptools会通过pkg_resources管理命名空间合并,即使子包有__init__.py,也能正确合并不同分布包的内容。
方案二:配置setuptools强制合并命名空间
修改两个包的pyproject.toml中tool.setuptools.packages.find部分,添加namespaces = true:
pkg/pyproject.toml修改后:
[tool.setuptools.packages.find] include = ["pkg*"] where = [".."] namespaces = true
subfolder/pyproject.toml修改后:
[tool.setuptools.packages.find] include = ["pkg*"] namespaces = true
该配置会告诉setuptools,即使包目录下存在__init__.py,也要将其当作命名空间包处理,实现不同分布包中同名子包的合并。需要确保你的setuptools版本(已满足>=61的要求)支持该选项。
另外要注意:如果两个包都有pkg/module/__init__.py,Python会加载第一个被找到的版本,建议保持两个文件内容一致,或在其中一个文件中导入另一个包的相关内容,避免逻辑冲突。
内容的提问来源于stack exchange,提问作者Intrastellar Explorer

