首先澄清一个常见误区:src目录结构并非什么新潮概念,但它解决的实际问题非常明确——如果直接将包代码放在项目根目录下,执行python main.py时可能会侥幸成功导入自己的模块,这是因为当前工作目录恰好是根目录。然而,当用户真正安装你的包后,import mypackage就会失败,因为根目录根本不在sys.path里。而src目录结构天然切断了这种侥幸:代码不在sys.path[0]中,必须通过正确的安装或路径配置才能导入,从而迫使开发环境与生产环境的行为保持一致。
pyproject.toml + src 是现代打包的事实标准
在pyproject.toml中只需声明如下配置:
[build-system]
requires = ["setuptools>=45", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "myapp"
# 其他字段...
[project.options.packages.find]
where = ["src"]
这样一来,执行pip install -e .后,系统会自动从src/目录下查找包,而不会误将tests/、scripts/或临时文件打包进去。从此你无需再编写setup.py,也无需手动维护MANIFEST.in来排除测试文件。
pytest 运行测试时不再需要 hack sys.path
常见的错误现象:ModuleNotFoundError: No module named 'myapp',尤其是在tests/test_core.py中写from myapp.core import do_something时。
- 错误做法:在
tests/__init__.py里添加sys.path.append("../src") - 正确做法:首先执行一次
pip install -e .,之后所有pytest调用都能正常解析绝对导入 - 在 CI/CD 流程中也只需这一条命令,无需额外的路径操作
IDE 和编辑器能更可靠地识别包结构
PyCharm、VS Code 的 Python 插件默认将src/视为源代码根目录——只要你在pyproject.toml中配置了where = ["src"],它们就能自动完成代码补全、跳转定义、类型检查等操作,而不会把tests/或docs/误当作可导入模块。
容易被忽略的一点是:src目录结构本身并不直接解决导入问题,它只是将“必须显式声明包可见性”这一约束提前暴露出来。一旦你接受了这个前提,后续的测试、打包、协作就会减少许多隐含的故障。
