现代 Python 包的标准结构
mypackage/
├── pyproject.toml 项目配置(替代旧的 setup.py)
├── README.md
├── LICENSE
├── src/
│ └── mypackage/
│ ├── __init__.py
│ ├── core.py
│ └── cli.py
└── tests/
└── test_core.py
注意 src/ 布局——避免"导入了开发目录而不是已安装的包"这种坑。
pyproject.toml 最小例子
[build-system]
requires = ["setuptools>=64", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "mypackage"
version = "0.1.0"
description = "我的酷酷的工具"
readme = "README.md"
license = { text = "MIT" }
authors = [{ name = "WadeLy", email = "hi@x.com" }]
requires-python = ">=3.10"
dependencies = [
"requests>=2.31",
"click>=8.1",
]
[project.optional-dependencies]
dev = ["pytest", "ruff", "mypy"]
[project.scripts]
mytool = "mypackage.cli:main" # 装包后产生 mytool 命令
[tool.setuptools.packages.find]
where = ["src"]
本地安装测试
# 开发模式:源码改动立即生效
pip install -e .
# 装可选依赖(dev)
pip install -e ".[dev]"
-e 是 editable——不复制文件,建软链。
构建发布包
pip install build
python -m build
# 产物在 dist/ 目录:
# dist/mypackage-0.1.0-py3-none-any.whl
# dist/mypackage-0.1.0.tar.gz
发到 PyPI
pip install twine
twine upload dist/*
# 测试发布到 TestPyPI
twine upload --repository testpypi dist/*
需要先在 pypi.org 注册账号 + 创建 API token。
poetry:一站式工具(推荐)
pip install poetry
poetry new mypackage # 建项目骨架
poetry add requests # 加依赖(自动写到 pyproject.toml)
poetry install # 装依赖到虚拟环境
poetry run pytest # 在虚拟环境跑命令
poetry build # 打包
poetry publish # 发布
依赖锁定到 poetry.lock——别人 clone 你的项目能装出完全一样的版本。
uv:2026 年的新选择
pip install uv
uv init mypackage
uv add requests
uv sync # 装依赖(比 pip 快 10–100 倍)
uv run pytest
Rust 写的,比 pip / poetry 快几个数量级。新项目优先考虑 uv。
init.py:包的"门面"
# src/mypackage/__init__.py
__version__ = "0.1.0"
from .core import main_function, MainClass
__all__ = ["main_function", "MainClass"]
外部用 from mypackage import main_function 直接引用——不用知道内部模块结构。
版本号:SemVer
0.1.0 三段式:
MAJOR.MINOR.PATCH- 不兼容改动 → MAJOR + 1
- 加功能不破坏 → MINOR + 1
- 修 bug → PATCH + 1
0.x.y= 实验阶段,可以乱改
选谁
| 场景 | 用 |
|---|---|
| 学习 / 极简 | setuptools + pyproject.toml |
| 现代项目 / 团队协作 | poetry |
| 追求极速 / 2026 新项目 | uv |
| 老 setup.py 项目 | 渐进迁移到 pyproject.toml |
最小 pyproject.toml + cli 完整例子
[project]
name = "todo"
version = "0.1.0"
dependencies = ["click>=8"]
[project.scripts]
todo = "todo.cli:main"
# src/todo/cli.py
import click
@click.command()
@click.argument("task")
def main(task):
print(f"加入: {task}")
pip install -e . 之后,终端可以直接 todo "买菜"。
下一篇讲 代码风格工具链。