06-发布自己的包
Python 3.11+
概念铺垫
6.1 为什么发布包
实际场景
你写了一个很棒的工具库 myutils,里面有各种常用函数。你的同事也想用,但他们不想每次都复制你的代码文件。
问题:如何让你的库像 pandas 一样,别人可以用 pip install 安装?
答案:打包发布到 PyPI。
项目结构
6.2 标准项目结构
标准结构
myproject/
├── src/
│ └── myproject/
│ ├── __init__.py
│ ├── core.py
│ └── utils.py
├── tests/
│ ├── __init__.py
│ └── test_core.py
├── docs/
│ └── index.md
├── pyproject.toml
├── README.md
├── LICENSE
└── .gitignore6.3 pyproject.toml 配置
现代 Python 项目使用 pyproject.toml 作为配置文件:
toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "myutils"
version = "0.1.0"
description = "A collection of useful Python utilities"
readme = "README.md"
requires-python = ">=3.11"
license = "MIT"
authors = [
{ name = "Your Name", email = "your@email.com" }
]
keywords = ["python", "utilities"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
dependencies = [
"requests>=2.28.0",
]
[project.optional-dependencies]
dev = ["pytest>=7.0.0", "ruff>=0.1.0"]
[project.urls]
Homepage = "https://github.com/user/myutils"
Repository = "https://github.com/user/myutils.git"
[project.scripts]
myutils = "myutils.cli:main"版本管理
6.7 语义化版本
MAJOR.MINOR.PATCH
MAJOR:不兼容的 API 变更
MINOR:向后兼容的功能新增
PATCH:向后兼容的问题修复
示例:
1.0.0 -> 1.0.1(修复 bug)
1.0.1 -> 1.1.0(新增功能)
1.1.0 -> 2.0.0(破坏性变更)L1 理解层:会用
README.md
markdown
# MyProject
一个示例 Python 项目。
## 安装
```bash
pip install myproject使用
python
from myproject import core
result = core.process("data")
print(result)文档
完整文档请访问:https://myproject.readthedocs.io
许可证
MIT License
## 打包流程
#### 安装打包工具
```bash
pip install build twine构建分发包
bash
# 构建源码包和 wheel 包
python -m build
# 生成的文件在 dist/ 目录
# dist/
# ├── myproject-1.0.0-py3-none-any.whl
# └── myproject-1.0.0.tar.gz检查包
bash
# 检查包的元数据
twine check dist/*
# 检查包的内容
tar -tzf dist/myproject-1.0.0.tar.gz
unzip -l dist/myproject-1.0.0-py3-none-any.whl发布到 PyPI
6.6 注册账号
- 访问 https://pypi.org/account/register/
- 创建账号并验证邮箱
- 创建 API Token(Account settings -> API tokens)
上传到 TestPyPI(测试)
bash
# 上传到 TestPyPI
twine upload --repository testpypi dist/*
# 从 TestPyPI 安装测试
pip install --index-url https://test.pypi.org/simple/ myproject上传到 PyPI(正式)
bash
# 上传到 PyPI
twine upload dist/*
# 输入用户名和密码
# 用户名:__token__
# 密码:pypi-xxx...(API Token)使用 API Token
bash
# 方式 1:命令行输入
twine upload dist/*
# Username: __token__
# Password: pypi-xxx...
# 方式 2:配置文件 ~/.pypirc
[pypi]
username = __token__
password = pypi-xxx...更新版本
toml
# pyproject.toml
[project]
version = "1.1.0" # 更新版本号bash
# 重新构建和上传
python -m build
twine upload dist/*自动化发布
GitHub Actions
yaml
# .github/workflows/publish.yml
name: Publish to PyPI
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
pip install build twine
- name: Build
run: python -m build
- name: Publish to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: twine upload dist/*L2 实践层:用好
项目结构建议
┌─────────────────────────────────────────────────────────────┐
│ 发布包的最佳实践 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 使用 src/ 布局 │
│ 避免导入问题,结构清晰 │
│ │
│ 2. 完善的文档 │
│ README、API 文档、使用示例 │
│ │
│ 3. 测试覆盖 │
│ 单元测试、集成测试 │
│ │
│ 4. 版本管理 │
│ 语义化版本、CHANGELOG │
│ │
│ 5. 持续集成 │
│ GitHub Actions、自动化测试和发布 │
│ │
└─────────────────────────────────────────────────────────────┘推荐做法
| 做法 | 原因 | 示例 |
|---|---|---|
使用 src/ 布局 | 避免导入混淆,防止测试时导入源码而非安装包 | src/myproject/ rather than myproject/ |
| 语义化版本号 | 清晰传达 API 变更级别 | 1.0.0 -> 1.1.0 (新增功能) -> 2.0.0 (不兼容变更) |
| 使用 API Token 而非密码 | 安全性更高,可撤销 | __token__ + pypi-xxx... |
| 先在 TestPyPI 测试发布 | 验证打包正确后再发布到正式 PyPI | twine upload --repository testpypi dist/* |
| 使用 GitHub Actions 自动化发布 | 减少人为失误,每次发布一致 | 触发条件:tag push 或 release published |
| 维护 CHANGELOG.md | 记录每个版本的变更 | 修复了什么 bug、新增了什么功能 |
| README 包含安装和快速上手 | 降低使用门槛 | pip install xxx + 3 行代码示例 |
反模式:不要这样做
bash
# ❌ 在正式 PyPI 上直接发布未测试的包
python -m build && twine upload dist/*
# 包有问题会导致用户安装到坏版本
# ✅ 先在 TestPyPI 测试
python -m build
twine upload --repository testpypi dist/*
# 验证通过
pip install --index-url https://test.pypi.org/simple/ myproject
# 确认无误后再发布正式版
twine upload dist/*
# ---
# ❌ 忘记更新版本号就重复发布
# pyproject.toml 中 version = "1.0.0"
python -m build
twine upload dist/*
# Error: File already exists ← 版本号重复!
# ✅ 发布前更新版本号
# 修改 pyproject.toml: version = "1.0.1"
# 或使用工具自动 bump:
# bump2version patch # 自动更新版本号
# ---
# ❌ 将敏感信息(token、密码)写入代码或提交到 git
# .pypirc 文件被 git add...
# ✅ 使用环境变量或 GitHub Secrets
# export TWINE_USERNAME=__token__
# export TWINE_PASSWORD=pypi-xxx...
# .pypirc 添加到 .gitignore
# ---
# ❌ 发布时包含不必要的文件
# dist/ 中含有 .pyc、.git、tests/ 等
# ✅ 使用 MANIFEST.in 控制打包内容
# MANIFEST.in:
# include README.md LICENSE
# exclude .gitignore
# prune tests适用场景
| 场景 | 推荐 | 原因 |
|---|---|---|
| 发布内部团队共享的库 | PyPI (私有索引) 或直接 Git 安装 | 团队内便捷使用 |
| 开源 Python 库 | PyPI + GitHub Actions 自动化 | 标准分发渠道 |
| 命令行工具 | pyproject.toml 中配置 [project.scripts] | pip install 后可命令行调用 |
| 纯内部工具(不对外) | 直接 pip install git+https://... | 无需发布到 PyPI |
| 学习/练习打包流程 | TestPyPI | 不影响正式 PyPI,可反复练习 |
| 多平台 wheel 包 | 使用 cibuildwheel | 自动为多平台构建 wheel |
本章小结
┌─────────────────────────────────────────────────────────────┐
│ 发布自己的包 知识要点 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 项目结构: │
│ ✓ src/ 布局 │
│ ✓ pyproject.toml 配置 │
│ ✓ README.md 文档 │
│ │
│ 打包: │
│ ✓ python -m build │
│ ✓ twine check │
│ │
│ 发布: │
│ ✓ twine upload dist/* │
│ ✓ TestPyPI 测试 -> PyPI 正式 │
│ │
│ 版本管理: │
│ ✓ 语义化版本 │
│ ✓ CHANGELOG 记录变更 │
│ │
│ 自动化: │
│ ✓ GitHub Actions │
│ │
└─────────────────────────────────────────────────────────────┘