
本文介绍在未发布型 Python 项目中,mypy 报错 “Duplicate module named” 的根本原因及推荐解决方案:通过在 pyproject.toml 中显式排除 build/ 目录,而非依赖 --explicit-package-bases 或 setuptools.py-modules = []。
本文介绍在未发布型 python 项目中,mypy 报错 “duplicate module named” 的根本原因及推荐解决方案:通过在 `pyproject.toml` 中显式排除 `build/` 目录,而非依赖 `--explicit-package-bases` 或 `setuptools.py-modules = []`。
该错误(如 Duplicate module named "hello" (also at "./build/lib/hello/__init__.py"))并非源于项目结构本身,而是由现代 pip(≥20.1)默认启用的就地构建(in-place build)机制引发的副作用:当执行 pip install '.[test]' '.[lint]' 时,pip 会先在项目根目录下创建 build/ 和 dist/ 目录,并将源码复制或链接至 build/lib/ 下(例如 build/lib/hello/__init__.py)。此时,mypy 默认会递归扫描整个工作目录,同时发现原始 hello/ 和 build/lib/hello/ 两个路径,将其解析为同一模块名 "hello",从而触发冲突警告。
虽然 --explicit-package-bases 可临时绕过该问题(它强制 mypy 仅从明确声明的包基路径解析模块),而 setuptools.py-modules = [] 能抑制 setuptools 自动推导顶层模块的行为,但二者均属间接干预,且存在潜在副作用:
-
--explicit-package-bases需全局启用,可能影响其他子包或工具链兼容性; -
setuptools.py-modules = []属于构建配置,与类型检查逻辑无关,在纯开发/CI 场景中属于职责越界。
✅ 真正符合最小改动、最大意图清晰原则的方案,是精准排除干扰路径:
在 pyproject.toml 的 mypy 配置中,将 build/ 明确加入 exclude 列表:
[tool.mypy]
exclude = [
"venv",
"build", # ? 关键修复:阻止 mypy 扫描构建产物
"__pycache__",
".pytest_cache",
]
ignore_errors = false
warn_return_any = true
disallow_untyped_defs = true
? 补充建议:你还可以在 CI 脚本中添加清理步骤(如
rm -rf build/ dist/),或使用pip install --no-build-isolation(需谨慎评估依赖隔离需求),但配置exclude是最轻量、最可靠、最符合 mypy 设计哲学的做法。
值得注意的是,mypy 当前(截至 v1.12)不会自动忽略 build/ 目录,这已被社区确认为体验缺陷(参见 mypy#18429)。因此,显式声明 exclude = ["build"] 不是“过度配置”,而是当前事实标准下的必要实践。对于不发布、仅本地/CI 使用的个人库,此方案零侵入、易维护、语义明确——它直击问题根源,而非掩盖症状。










