本文为AI帮者写的。
在 Python 开发中,“能跑就行”和“工程化”之间往往只隔着一个合理的目录结构。很多开发者在项目初期随意摆放文件,导致后期出现循环导入、打包困难、路径混乱等问题。本文将从零开始,带你打造一个专业级的 Python 工程,涵盖目录结构、模块导入、开发模式安装,以及 VSCode 的完美配置。
1. 为什么推荐 src/ 目录结构?
很多初学者习惯将代码直接放在项目根目录下(扁平结构),但对于中大型项目或需要打包发布的库,src/ 结构是行业标准(Django、Pandas、Flit 均采用此结构)。它能有效隔离源代码与项目配置,避免许多隐式错误。
1.1 标准结构对比
❌ 不推荐的扁平结构(易出错):
my_project/
├── my_package/ # 包
│ ├── __init__.py
│ └── module.py
├── main.py # 入口脚本
└── setup.py
风险:运行 python main.py 时,Python 会将当前目录 my_project/ 加入 sys.path。如果 my_package 和 main.py 互相导入,极易引发循环导入或命名空间污染。此外,tests/ 目录混在根目录下,打包时可能被意外包含。
✅ 推荐的 src 结构:
my_project/
├── src/ # 源代码根目录
│ └── my_package/ # 实际的包
│ ├── __init__.py
│ ├── module_a.py
│ └── sub_package/
│ ├── __init__.py
│ └── module_b.py
├── tests/ # 测试目录
├── .gitignore
├── pyproject.toml # 现代打包配置(替代 setup.py)
├── README.md
└── LICENSE
1.2 src/ 的核心优势
1.3 src/ 带来的“麻烦”及解决方案
src/ 结构确实增加了一点复杂度:直接运行 python src/my_package/main.py 会报 ModuleNotFoundError。
解决方案:
pip install -e .
这样 Python 环境会链接到 src/,之后你可以像普通包一样 import my_package。
python -m my_package.main
2. 模块引用指南:相对导入 vs 绝对导入
在 src/ 结构下,理解导入语法至关重要。
2.1 核心符号含义
在 from .文件名 import … 中:
- . (单点):代表当前包(当前目录)。
- .. (双点):代表父级包(上一级目录)。
- 限制:只能在包内的模块中使用(即目录必须有 __init__.py,Python 3.3+ 支持隐式命名空间包,但建议显式创建 __init__.py 以明确包边界)。
- 禁忌:不能在顶层脚本(直接运行的 .py 文件)中使用,否则报错 ImportError: attempted relative import with no known parent package。
2.2 实战场景演示
假设结构如下:
src/
└── my_package/
├── __init__.py
├── module_a.py
└── sub_package/
├── __init__.py
└── module_b.py
场景 A:同级模块引用
需求:在 module_a.py 中导入 sub_package/module_b.py。
# src/my_package/module_a.py
# 错误 ❌: from module_b import x (会去系统路径找,找不到)
# 正确 ✅: 从当前包(my_package)进入 sub_package
from .sub_package.module_b import some_function
场景 B:下级模块引用(父引用子)
同上,也是相对导入的一种。
场景 C:上级/跨级引用(子引用父)
需求:在 module_b.py 中导入 module_a.py。
# src/my_package/sub_package/module_b.py
# .. 表示返回上一级包 (my_package)
from ..module_a import some_function
场景 D:顶层脚本引用包(绝对导入)
需求:在项目根目录的 main.py 或外部脚本中引用。
# main.py (位于项目根目录,非 src 内)
# 必须使用绝对导入
from my_package.module_a import some_function
from my_package.sub_package.module_b import another_function
# 严禁使用: from .my_package import … (会报错)
2.3 相对导入 vs 绝对导入 对比表
| 相对导入 | from .module import xfrom ..sub import y | 包内部模块互引 | 重构方便(改包名不影响内部) | 顶层脚本不可用;路径深时可读性差 |
| 绝对导入 | from my_package.module import x | 顶层脚本、跨包引用 | 路径清晰;全局可用 | 包名重构需全局替换 |
最佳实践建议:
- 包内部(.py 之间):优先用相对导入(from . import)。
- 包外部(脚本引用包):必须用绝对导入(from my_package import)。
3. 开发神器:pip install -e (可编辑模式)
当你采用 src/ 结构或开发一个库时,pip install -e . 是必备技能。
3.1 它是做什么的?
- -e 是 –editable 的缩写。
- 普通安装 (pip install .):将代码复制到 Python 的 site-packages 目录。修改源码后需重新安装才生效。
- 可编辑安装 (pip install -e .):在 site-packages 中创建一个链接文件(.egg-link 或 .pth),指向你的本地源码路径。
3.2 核心价值
修改代码,立即生效,无需重装!
3.3 适用场景
3.4 注意事项
- 必须有打包配置:项目需包含 setup.py 或3.6+PEP 518版本之后的 pyproject.toml(推荐)。
- 路径敏感:如果移动了项目文件夹,链接会失效,需重新安装。
- 建议用虚拟环境:避免污染全局环境,方便随时删除重试。
- 卸载:使用 pip uninstall 包名 即可移除链接。
4. 标准工程及 VSCode 配置全攻略
假设我们的项目结构如下:
my_awesome_project/
├── .vscode/ # VSCode 配置目录(建议加入 .gitignore)
│ ├── settings.json
│ └── launch.json
├── src/
│ └── my_awesome_package/
│ ├── __init__.py
│ ├── core.py
│ ├── utils/
│ │ ├── __init__.py
│ │ └── helpers.py
│ └── main.py # 可选入口
├── tests/
│ ├── __init__.py
│ └── test_core.py
├── .gitignore
├── pyproject.toml # 现代打包配置
└── README.md
代码内容:
src/my_awesome_package/utils/helpers.py
def helper_func():
return "I am a helper"
src/my_awesome_package/core.py (引用子模块)
# 从同级的 utils 子包导入 helpers
from .utils.helpers import helper_func
def main_logic():
print(f"Core logic calling: {helper_func()}")
src/my_awesome_package/main.py (包内入口,引用同级)
from .core import main_logic
if __name__ == "__main__":
# 注意:这里不能用相对导入,因为这是直接运行的脚本
# 但因为安装了包,可以用绝对导入,或者用 -m 运行
main_logic()
pyproject.toml (现代 Python 打包配置)
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "my_awesome_package"
version = "0.1.0"
description = "A fantastic package"
readme = "README.md"
requires-python = ">=3.8"
license = {text = "MIT"}
[tool.setuptools.packages.find]
where = ["src"]
如何运行与开发:
# 1. 创建虚拟环境
python -m venv venv
source venv/bin/activate # Windows: venv\\Scripts\\activate
# 2. 可编辑安装 (关键步骤!)
pip install -e .
# 3. 安装开发依赖(如 pytest)
pip install pytest black
# 4. 运行测试或脚本
python -m my_awesome_package.main
pytest tests/
4.1 选择解释器 (Select Interpreter)
这是第一步,也是最重要的一步。
- 关键:确保你已经在终端运行了 pip install -e .,这样 Python 环境才能识别 my_awesome_package。
4.2 配置智能提示与路径识别 (settings.json)
如果不配置,VSCode 的 Pylance 可能会在 from my_awesome_package import … 下画红线,提示 Import "my_awesome_package" could not be resolved。
解决方法:在项目根目录创建 .vscode/settings.json,添加 python.analysis.extraPaths。
{
"python.analysis.extraPaths": ["./src"],
"python.testing.pytestArgs": ["tests"],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"editor.formatOnSave": true,
"editor.defaultFormatter": "ms-python.black-formatter",
"[python]": {
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
}
}
核心配置解释:
- "python.analysis.extraPaths": ["./src"]:
- 告诉 Pylance:“请把 ./src 目录当作源码根目录去扫描”。这样 from my_awesome_package.core import … 就不会报错了。
- 测试配置:启用 pytest 并指定测试目录。
- 格式化配置:保存时自动用 Black 格式化,并自动整理 import(需安装 isort 插件或使用 Black 结合)。
4.3 配置调试与运行 (launch.json)
在 src/ 结构下,直接按 F5 运行当前打开的文件(如 src/my_awesome_package/main.py)通常会失败,因为 Python 会把当前文件所在目录加入路径,导致相对导入混乱。
正确做法:使用 module 模式,模拟 python -m 命令。
在 .vscode/ 下创建 launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: 运行主程序 (Module模式)",
"type": "python",
"request": "launch",
"module": "my_awesome_package.main",
"console": "integratedTerminal",
"justMyCode": true,
"cwd": "${workspaceFolder}"
},
{
"name": "Python: 调试当前文件 (谨慎使用)",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"env": {
"PYTHONPATH": "${workspaceFolder}/src"
},
"justMyCode": true
},
{
"name": "Python: 调试当前测试",
"type": "python",
"request": "launch",
"program": "${file}",
"purpose": ["debug-test"],
"console": "integratedTerminal"
},
{
"name": "Python: 运行所有测试 (Pytest)",
"type": "python",
"request": "launch",
"module": "pytest",
"args": ["-v", "tests/"],
"console": "integratedTerminal"
}
]
}
配置详解:
- 这等同于在终端执行 python -m my_awesome_package.main。VSCode 会自动处理 sys.path,确保能正确找到包。
- 注意:这里不需要写 src. 前缀,因为包已经安装到了环境。
4.4 集成测试流程
配置好 settings.json 中的 pytest 参数后:
如果测试代码中需要导入源码:
# tests/test_core.py
from my_awesome_package.core import main_logic
def test_main_logic():
assert main_logic() is not None # 假设 main_logic 返回 None,此处仅为示例
5. 完整开发工作流 (Cheat Sheet)
5.1 初始化项目
mkdir my_awesome_project && cd my_awesome_project
mkdir -p src/my_awesome_package tests .vscode
touch src/my_awesome_package/__init__.py
touch tests/__init__.py
# 创建其他文件…
5.2 设置虚拟环境与依赖
python -m venv venv
source venv/bin/activate # Windows: venv\\Scripts\\activate
pip install –upgrade pip
pip install -e . # 可编辑安装你的包
pip install pytest black isort # 安装开发工具
pip freeze > requirements-dev.txt # 可选,保存开发依赖
5.3 VSCode 配置
- 创建 .vscode/settings.json (配置 extraPaths、格式化等)。
- 创建 .vscode/launch.json (配置 module 运行模式)。
- 安装推荐插件:Python (by Microsoft), Pylance, Black Formatter.
5.4 编写代码与调试
- 写代码:在 src/my_awesome_package/ 下编写,利用 Pylance 的自动补全。
- 运行:按 F5 选择 “Python: 运行主程序 (Module模式)”。
- 调试:在代码行号左侧打红点,按 F5 启动调试。
- 测试:在 tests/ 下写测试用例,利用侧边栏 Test 图标运行。
6. 常见 VSCode 问题排查
| 导入报红波浪线 | Pylance 没找到 src/ | 检查 .vscode/settings.json 的 extraPaths 是否为 ["./src"] |
| 调试时 ModuleNotFound | 运行时路径不对 | 不要用 "program": "${file}" 运行包内文件,改用 "module": "package.module" |
| 找不到 pytest | 解释器没选对 | Ctrl+Shift+P -> Python: Select Interpreter,选 venv 里的 Python |
| 相对导入报错 | 直接运行了包内文件 | 不要右键点击 src/ 下的文件选 “Run Python File”,要用 F5 配合 launch.json 的 module 模式 |
| Pylance 报错但代码能运行 | 缺少 extraPaths | 添加 extraPaths 即可消除红线,但代码本身能运行说明路径已通过安装解决 |
| 保存时没有自动格式化 | 未设置默认格式化器 | 安装 Black 插件,并在 settings.json 中设置 "editor.defaultFormatter": "ms-python.black-formatter" |
7. 总结
- settings.json -> extraPaths 解决 智能提示。
- launch.json -> module 模式解决 运行/调试。
配置好这些后,你的 Python 工程将拥有专业级的开发体验:代码提示精准、调试顺畅、测试自动化。现在就动手重构你的项目吧! 🚀



