
本文详解如何通过 `pip install -e .` 正确安装采用“ad-hoc 布局”的本地 python 项目,解决 `find_packages(where='mypkg')` 报错及子包不可导入的问题,关键在于配置 `package_dir` 映射源码根目录与实际包路径。
在开发多层嵌套的本地 Python 项目时(如 mypkg/subpkg1/, mypkg/subpkg2/),常希望仅执行一条命令(如 pip install -e .)即可完成可编辑安装,并让所有带 __init__.py 的子目录自动作为可导入子包。但若项目结构为典型的 ad-hoc layout(即包代码位于子目录 mypkg/ 下,而非与 setup.py 同级),直接使用 find_packages() 很容易失败——典型报错如:
error: package directory 'subpkg1' does not exist
该错误并非因为目录真实缺失,而是 setuptools.find_packages() 默认在当前目录(即 project_root_directory)下查找包,而你的包实际位于 mypkg/ 子目录中。此时,仅靠 find_packages(where='mypkg') 是不够的:它能发现 mypkg.subpkg1 等包,但 setuptools 仍会尝试从项目根路径解析模块路径,导致路径映射错位。
✅ 正确解法是同时声明 package_dir,显式告诉 setuptools:“当导入 mypkg 及其子包时,请从 mypkg/ 目录下加载源码”。
修改你的 setup.py 如下:
立即学习“Python免费学习笔记(深入)”;
from setuptools import setup, find_packages
setup(
name="Code",
author="Me",
author_email="me@example.com",
description="My local Python project",
package_dir={"": "mypkg"}, # ? 关键!将空字符串(即顶层包名)映射到 'mypkg' 目录
packages=find_packages(where="mypkg"), # 在 mypkg/ 下自动发现所有子包
python_requires=">=3.6",
)? 补充说明:package_dir={"": "mypkg"} 表示“所有未指定前缀的包(包括 mypkg 本身)均从 mypkg/ 目录读取”。这样 find_packages(where="mypkg") 才能与安装时的路径解析逻辑对齐。
✅ 安装验证:
pip install -e .
安装成功后,即可在 Python 中正常导入:
from mypkg import module # ✔️ 主包 from mypkg.subpkg1 import module1 # ✔️ 子包 import mypkg.subpkg2.module2 # ✔️ 深层嵌套
⚠️ 注意事项:
- __init__.py 文件必须存在且非空(至少含 __all__ 或 pass),否则 find_packages() 会跳过该目录;
- 若使用 pyproject.toml(推荐现代项目),应改用 setuptools 配置块,并保留等效逻辑([project] + [tool.setuptools]);
- IDE(如 VS Code、PyCharm)可能因缓存延迟无法立即支持 tab 补全,重启 Python 解释器或刷新环境后通常可恢复;
- 避免在 packages 中手动列出子包名(如 ["mypkg", "mypkg.subpkg1"]),这会绕过自动发现机制,丧失可维护性。
总结:package_dir={"": "mypkg"} 是 ad-hoc 布局下实现一键可编辑安装与完整子包可见性的核心配置。它桥接了物理目录结构与 Python 包命名空间,是本地开发中兼顾简洁性与规范性的最佳实践。










