1. Python 工程化项目骨架搭建
为什么需要工程骨架
一个可安装、可测试、可发布的 Python 工程,核心不在于代码写得多,而在于目录布局、构建元数据和包发现规则三者一致。本案例采用当前社区推荐的 src 布局:源码放在 src/ 下,测试放在 tests/ 下,构建配置集中在 pyproject.toml。这样做的直接好处是:测试必须针对“已安装的包”运行,而不是意外导入当前目录下的同名文件夹,从而提前暴露打包遗漏。
创建骨架文件
先建立目录与最小文件集。以下命令在空目录中执行,创建 myproj 工程:
mkdir -p myproj/src/myproj myproj/tests
cd myproj
touch src/myproj/__init__.py src/myproj/core.py tests/test_core.py README.md
写入包源码 src/myproj/core.py,提供一个可被测试的最小函数:
def add(a: int, b: int) -> int:
"""返回两数之和。"""
return a + b
def slugify(text: str) -> str:
"""将文本转为小写连字符形式。"""
return "-".join(text.lower().split())
src/myproj/__init__.py 暴露版本号,便于安装后验证:
__version__ = "0.1.0"
from .core import add, slugify
__all__ = ["add", "slugify", "__version__"]
编写 pyproject.toml
这是整个骨架的核心。它同时承担构建系统声明、项目元数据、依赖声明和包发现配置四项职责:
[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "myproj"
version = "0.1.0"
description = "一个可安装、可测试、可发布的 Python 工程骨架示例"
readme = "README.md"
requires-python = ">=3.9"
license = { text = "MIT" }
authors = [{ name = "Example Author", email = "author@example.com" }]
dependencies = []
[project.optional-dependencies]
dev = ["pytest>=7.0", "build>=1.0"]
[tool.setuptools]
package-dir = { "" = "src" }
[tool.setuptools.packages.find]
where = ["src"]
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-q"
package-dir = { "" = "src" } 与 packages.find.where = ["src"] 必须同时存在:前者告诉构建后端“根包在 src 下”,后者告诉它“去 src 里扫描包”。只写其中一个会导致安装后 import myproj 失败或打包为空。
编写测试
测试文件 tests/test_core.py 直接导入包名,而不是相对路径导入:
from myproj import add, slugify, __version__
def test_add():
assert add(2, 3) == 5
def test_slugify():
assert slugify("Hello World") == "hello-world"
def test_version():
assert __version__ == "0.1.0"
可编辑安装与验证
在工程根目录创建虚拟环境并执行可编辑安装:
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
python -m pip install --upgrade pip
pip install -e ".[dev]"
安装完成后依次验证三件事:
- 包可导入:
python -c "import myproj; print(myproj.__version__)"应输出0.1.0。 - 测试通过:
pytest应显示 3 passed。 - 可构建发布物:
python -m build应在dist/下生成myproj-0.1.0-py3-none-any.whl与.tar.gz。
若
pytest 报 ModuleNotFoundError: No module named 'myproj',通常是因为在未激活虚拟环境或未执行 pip install -e . 的情况下直接运行测试。src 布局的设计意图正是让这种错误暴露出来,而不是被当前目录的隐式导入掩盖。
验证清单
| 检查项 | 命令 | 通过标准 |
|---|---|---|
| 包可导入 | python -c "import myproj" | 无报错 |
| 版本正确 | python -c "import myproj; print(myproj.__version__)" | 输出 0.1.0 |
| 测试通过 | pytest | 3 passed |
| 可构建 | python -m build | dist/ 下生成 whl 与 tar.gz |
| wheel 内容正确 | python -m zipfile -l dist/*.whl | 包含 myproj/__init__.py 与 core.py |
至此,一个具备标准目录结构、集中式构建配置、可编辑安装路径和测试入口的 Python 工程骨架已经就绪。后续添加命令行入口、依赖锁定或 CI 配置,都可以在这个骨架上直接扩展。