构建 Python 包:从开发到 PyPI 的指南

从代码到 PyPI 部署,掌握 Python 包管理

目录

Python打包 已经有了显著的发展,现代工具和标准使得分发你的代码比以往任何时候都更容易。

本指南将带你逐步构建专业的Python包并将其发布到PyPI。如果你是Python新手,或者需要快速参考,请查看我们的Python速查表,以快速掌握Python基础知识。

Python包

为什么打包你的Python代码?

打包你的Python项目有多个好处:

  • 可重用性:无需复制粘贴,即可在多个项目中共享代码
  • 分发:通过简单的pip install,让其他人安装你的代码
  • 依赖管理:明确指定并管理依赖项
  • 版本控制:跟踪发布版本并维护向后兼容性
  • 专业标准:遵循Python社区的最佳实践
  • 文档:结构鼓励适当的文档和测试

现代Python包结构

一个组织良好的包遵循以下结构:

my-package/
├── src/
│   └── mypackage/
│       ├── __init__.py
│       ├── core.py
│       └── utils.py
├── tests/
│   ├── __init__.py
│   └── test_core.py
├── docs/
│   └── index.md
├── .github/
│   └── workflows/
│       └── publish.yml
├── pyproject.toml
├── README.md
├── LICENSE
└── .gitignore

src-layout 现在比扁平布局更受推荐,因为它:

  • 防止在开发过程中意外导入未安装的代码
  • 通过强制安装使测试更加可靠
  • 清晰地将源代码与其他项目文件分开

在组织包的内部结构时,考虑应用干净的架构原则,以使你的代码更易于维护和测试。我们的指南Python清洁架构设计模式涵盖了与Python包配合出色的SOLID原则、依赖注入和分层架构模式。

pyproject.toml 文件:现代配置

pyproject.toml (PEP 518, 621) 是Python项目配置的现代标准,取代了传统的 setup.py:

[build-system]
requires = ["setuptools>=68.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "mypackage"
version = "0.1.0"
description = "一个出色的Python包"
readme = "README.md"
requires-python = ">=3.8"
license = {text = "MIT"}
authors = [
    {name = "你的名字", email = "you@example.com"}
]
keywords = ["示例", "教程", "包"]
classifiers = [
    "开发状态 :: 4 - 测试版",
    "目标受众 :: 开发者",
    "许可证 :: OSI 批准 :: MIT 许可证",
    "编程语言 :: Python :: 3",
    "编程语言 :: Python :: 3.8",
    "编程语言 :: Python :: 3.9",
    "编程语言 :: Python :: 3.10",
    "编程语言 :: Python :: 3.11",
    "编程语言 :: Python :: 3.12",
]
dependencies = [
    "requests>=2.28.0",
    "click>=8.1.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.4.0",
    "black>=23.0.0",
    "flake8>=6.0.0",
    "mypy>=1.5.0",
]
docs = [
    "sphinx>=7.0.0",
    "sphinx-rtd-theme>=1.3.0",
]

[project.urls]
Homepage = "https://github.com/yourusername/mypackage"
Documentation = "https://mypackage.readthedocs.io"
Repository = "https://github.com/yourusername/mypackage"
Issues = "https://github.com/yourusername/mypackage/issues"

[project.scripts]
mypackage-cli = "mypackage.cli:main"

[tool.setuptools.packages.find]
where = ["src"]

关键配置部分

  1. build-system:指定构建后端(setuptools、hatchling、poetry-core、flit-core)
  2. project:核心元数据和依赖项
  3. project.optional-dependencies:额外功能依赖项(开发、文档、测试)
  4. project.scripts:命令行入口点
  5. tool.*:工具特定配置(black、pytest、mypy 等)

选择构建后端

Setuptools(标准选择)

最广泛使用且兼容的选项:

[build-system]
requires = ["setuptools>=68.0", "wheel"]
build-backend = "setuptools.build_meta"

优点:通用兼容性、功能丰富、生态系统庞大 缺点:配置更冗长、比新选项慢

Hatchling(现代且快速)

轻量且高性能的现代后端:

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

优点:快速构建、简单配置、良好的默认值 缺点:插件比setuptools少

Poetry(一站式)

完整的依赖和环境管理:

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "mypackage"
version = "0.1.0"
description = "一个出色的Python包"
authors = ["你的名字 <you@example.com>"]

[tool.poetry.dependencies]
python = "^3.8"
requests = "^2.28.0"

优点:依赖解析、锁定文件、集成工作流程 缺点:不同的工作流程、较大的工具表面区域

Flit(极简)

用于简单包的简单工具:

[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"

优点:极其简单、最小配置 缺点:对复杂包功能有限

构建你的包

安装构建工具

# 安装现代构建工具
pip install build

# 或者使用Poetry
pip install poetry

# 或者使用Hatchling
pip install hatch

构建发行文件

使用 build 包(适用于任何后端):

# 清除之前的构建
rm -rf dist/ build/ *.egg-info

# 构建源代码发行版和wheel
python -m build

# 这会创建:
# dist/mypackage-0.1.0.tar.gz    (源代码发行版)
# dist/mypackage-0.1.0-py3-none-any.whl  (wheel)

使用Poetry:

poetry build

使用Hatch:

hatch build

理解发行格式

源代码发行版 (sdist) - .tar.gz

  • 包含源代码和构建说明
  • 用户的 pip 在安装时构建它
  • 包含测试、文档和其他开发文件

Wheel - .whl

  • 预构建的二进制发行版
  • 安装速度快(无构建步骤)
  • 平台特定或纯Python
  • 推荐的发行格式

发布到PyPI

方法1:使用Twine手动上传(传统方法)

# 安装twine
pip install twine

# 上传前检查包
twine check dist/*

# 首先上传到TestPyPI(推荐)
twine upload --repository testpypi dist/*

# 从TestPyPI测试安装
pip install --index-url https://test.pypi.org/simple/ mypackage

# 上传到生产PyPI
twine upload dist/*

~/.pypirc 中配置凭据:

[distutils]
index-servers =
    pypi
    testpypi

[pypi]
username = __token__
password = pypi-AgEIcHlwaS5vcmc...

[testpypi]
repository = https://test.pypi.org/legacy/
username = __token__
password = pypi-AgENdGVzdC5weXBpLm9yZw...

方法2:信任发布(推荐用于CI/CD)

信任发布使用OpenID Connect (OIDC) 从CI/CD平台进行身份验证,无需存储令牌。

在PyPI中设置:

  1. 前往你的PyPI项目设置
  2. 导航到“发布”部分
  3. 添加一个新的“待发布者”
  4. 配置:
    • 所有者:你的GitHub用户名/组织
    • 仓库:仓库名称
    • 工作流程publish.yml
    • 环境release(可选但推荐)

GitHub Actions工作流程.github/workflows/publish.yml):

name: 发布到PyPI

on:
  release:
    types: [published]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: 设置Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      
      - name: 安装构建依赖项
        run: |
          python -m pip install --upgrade pip
          pip install build          
      
      - name: 构建包
        run: python -m build
      
      - name: 存储发行包
        uses: actions/upload-artifact@v3
        with:
          name: python-package-distributions
          path: dist/

  publish:
    needs: build
    runs-on: ubuntu-latest
    environment: release
    permissions:
      id-token: write
    
    steps:
      - name: 下载发行包
        uses: actions/download-artifact@v3
        with:
          name: python-package-distributions
          path: dist/
      
      - name: 发布到PyPI
        uses: pypa/gh-action-pypi-publish@release/v1

优点:

  • 无需管理或保护API令牌
  • 通过OIDC自动认证
  • 通过环境保护规则增强安全性
  • 所有发布都有审计跟踪

最佳实践

1. 版本管理

使用语义版本控制(SemVer):MAJOR.MINOR.PATCH

# 安装版本提升工具
pip install bump2version

# 提升版本
bump2version patch  # 0.1.0 -> 0.1.1
bump2version minor  # 0.1.1 -> 0.2.0
bump2version major  # 0.2.0 -> 1.0.0

.bumpversion.cfg 中配置:

[bumpversion]
current_version = 0.1.0
commit = True
tag = True

[bumpversion:file:pyproject.toml]
search = version = "{current_version}"
replace = version = "{new_version}"

[bumpversion:file:src/mypackage/__init__.py]
search = __version__ = "{current_version}"
replace = __version__ = "{new_version}"

2. 包含必要的文件

README.md:清晰的项目描述、安装、使用示例 LICENSE:选择适当的许可证(MIT、Apache 2.0、GPL 等) CHANGELOG.md:记录版本之间的更改 .gitignore:排除构建产物、缓存、虚拟环境

3. 全面的测试

# 安装测试依赖项
pip install pytest pytest-cov

# 带覆盖率运行测试
pytest --cov=mypackage tests/

# 生成覆盖率报告
pytest --cov=mypackage --cov-report=html tests/

pyproject.toml 中配置:

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_functions = ["test_*"]
addopts = "--strict-markers --cov=mypackage"

4. 代码质量工具

# 格式化
black src/ tests/

# 检查代码风格
flake8 src/ tests/

# 类型检查
mypy src/

# 导入排序
isort src/ tests/

将它们集成到pre-commit钩子中(.pre-commit-config.yaml):

repos:
  - repo: https://github.com/psf/black
    rev: 23.9.1
    hooks:
      - id: black
  
  - repo: https://github.com/pycqa/flake8
    rev: 6.1.0
    hooks:
      - id: flake8
  
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.5.1
    hooks:
      - id: mypy

5. 文档

使用Sphinx进行文档编写:

# 安装Sphinx
pip install sphinx sphinx-rtd-theme

# 初始化文档
cd docs
sphinx-quickstart

# 构建文档
make html

或使用MkDocs进行更简单的Markdown文档:

pip install mkdocs mkdocs-material
mkdocs new .
mkdocs serve

6. 持续集成

在不同平台和环境中测试你的Python包对于可靠性至关重要。关于Python在不同部署场景中的性能,参见我们的比较文章 AWS Lambda性能:JavaScript vs Python vs Golang,该文章探讨了Python在无服务器环境中的性能表现。

完整的CI/CD工作流程(.github/workflows/ci.yml):

name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
    
    steps:
      - uses: actions/checkout@v4
      
      - name: 设置Python
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}
      
      - name: 安装依赖项
        run: |
          python -m pip install --upgrade pip
          pip install -e ".[dev]"          
      
      - name: 运行测试
        run: pytest --cov=mypackage --cov-report=xml
      
      - name: 上传覆盖率
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage.xml
  
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: 设置Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      
      - name: 安装依赖项
        run: |
          pip install black flake8 mypy          
      
      - name: Black格式检查
        run: black --check src/ tests/
      
      - name: Flake8
        run: flake8 src/ tests/
      
      - name: MyPy
        run: mypy src/

常见问题及解决方案

问题1:安装后导入错误

问题:包已安装但导入失败

解决方案:确保使用 __init__.py 文件和正确的 pyproject.toml 配置进行正确的包结构:

[tool.setuptools.packages.find]
where = ["src"]
include = ["mypackage*"]

问题2:缺少依赖项

问题:包安装后由于缺少依赖项而在运行时失败

解决方案:在 pyproject.toml 中声明所有运行时依赖项:

[project]
dependencies = [
    "requests>=2.28.0",
    "click>=8.1.0",
]

问题3:版本冲突

问题:包在开发中工作但在生产中失败

解决方案:使用虚拟环境并指定最低版本:

# 创建隔离环境
python -m venv .venv
source .venv/bin/activate  # 在Windows上:.venv\Scripts\activate

# 以可编辑模式安装用于开发
pip install -e ".[dev]"

问题4:包体积过大

问题:包下载/安装时间过长

解决方案:使用 MANIFEST.in 排除不必要的文件:

include README.md
include LICENSE
include pyproject.toml
recursive-include src *.py
recursive-exclude * __pycache__
recursive-exclude * *.py[co]
recursive-exclude tests *
recursive-exclude docs *

问题5:平台特定问题

问题:包在一个操作系统上工作但在另一个上失败

解决方案:使用CI/CD矩阵构建在多个平台上进行测试(参见上面的CI示例)

发布检查清单

在将包发布到PyPI之前,请验证以下内容:

  • 所有测试在Python版本上通过
  • 代码已格式化并经过检查
  • README.md 完整并包含示例
  • 包含LICENSE文件
  • 版本号已更新
  • CHANGELOG.md 已更新
  • 依赖项已正确指定
  • 包构建无错误 (python -m build)
  • 在TestPyPI上测试了包
  • 文档已更新
  • Git仓库已用版本标记
  • 创建了GitHub发布(用于信任发布)

高级主题

入口点和CLI工具

创建命令行接口:

[project.scripts]
mypackage = "mypackage.cli:main"

src/mypackage/cli.py 中的实现:

import click

@click.command()
@click.option('--name', default='World', help='要问候的名字')
def main(name):
    """简单的CLI工具示例"""
    click.echo(f'Hello, {name}!')

if __name__ == '__main__':
    main()

对于创建与外部API交互的Python包的现实示例,请参阅我们的指南 将Ollama与Python集成,该指南演示了使用REST API和官方库集成构建Python客户端。

插件系统

启用插件发现:

[project.entry-points."mypackage.plugins"]
plugin_a = "mypackage_plugin_a:PluginA"

C扩展

对于性能关键代码:

[tool.setuptools.ext-modules]
name = "mypackage._speedups"
sources = ["src/mypackage/_speedups.c"]

命名空间包

对于在共同命名空间下的分布式包:

[tool.setuptools.packages.find]
where = ["src"]
include = ["company.*"]

[tool.setuptools.package-data]
"company.mypackage" = ["py.typed"]

有用的工具和资源

包开发工具

  • cookiecutter:Python包的项目模板
  • tox:跨Python版本的测试自动化
  • nox:灵活的测试自动化(tox的替代)
  • pre-commit:代码质量的Git钩子框架
  • commitizen:标准化提交信息和版本控制

文档平台

  • Read the Docs:免费文档托管
  • GitHub Pages:托管MkDocs或Sphinx文档
  • pdoc:简单的API文档生成器

包注册表

  • PyPI:官方Python包索引(pypi.org)
  • TestPyPI:测试环境(test.pypi.org)
  • Anaconda.org:Conda包分发
  • GitHub Packages:私有包托管

监控和分析

  • PyPI Stats:包的下载统计
  • Libraries.io:依赖监控和警报
  • Snyk:安全漏洞扫描
  • Dependabot:自动依赖更新

结论

现代Python打包已经发展为更加开发者友好,采用如 pyproject.toml 的标准、强大的构建后端以及通过信任发布进行安全发布。遵循本指南,你可以创建专业、可维护的Python包,有效地服务于你的用户。

关键要点:

  1. 所有新项目使用 pyproject.toml
  2. 根据需求选择合适的 构建后端
  3. 实施 自动化测试 和CI/CD
  4. 使用 信任发布 进行安全、无令牌的部署
  5. 遵循 语义版本控制 并维护变更日志
  6. 提供全面的 文档 和示例

Python打包生态系统继续改进,具有更好的工具、标准和安全功能。保持更新Python增强提案(PEPs)和社区最佳实践,以确保你的包现代且可维护。

有用的链接

本网站上的其他有用文章