You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何__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'

问题

  1. 为何__init__.py会破坏命名空间包的安装与导入?
  2. 是否存在可行的解决方案,既能保留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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 02:55:08