Skip to content

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
└── .gitignore

6.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 注册账号

  1. 访问 https://pypi.org/account/register/
  2. 创建账号并验证邮箱
  3. 创建 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 测试发布验证打包正确后再发布到正式 PyPItwine 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                                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘