引言
从脚本到可安装包,打包是分发的前提。本文讲解现代Python打包规范pyproject.toml、wheel构建、私有PyPI搭建和CI发布。
一、打包的历史
setup.py:传统方式,执行代码,不安全setup.cfg:声明式,但仍需setup.pypyproject.toml:PEP 518/621,现代标准
二、pyproject.toml
toml
复制
下载
[build-system] requires = ["setuptools>=61.0", "wheel"] build-backend = "setuptools.build_meta" [project] name = "mypackage" version = "1.0.0" description = "一个示例包" readme = "README.md" requires-python = ">=3.9" license = {text = "MIT"} authors = [ {name = "Your Name", email = "you@example.com"}, ] keywords = ["example", "demo"] classifiers = [ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", ] dependencies = [ "requests>=2.28", "pydantic>=2.0", ] [project.optional-dependencies] dev = ["pytest", "ruff", "mypy"] docs = ["sphinx"] [project.scripts] mytool = "mypackage.cli:main" [project.urls] Homepage = "https://github.com/user/mypackage" Repository = "https://github.com/user/mypackage" [tool.setuptools.packages.find] where = ["src"] [tool.setuptools.package-data] mypackage = ["templates/*.html"]项目结构:
text
复制
下载
mypackage/ ├── pyproject.toml ├── README.md ├── src/ │ └── mypackage/ │ ├── __init__.py │ ├── core.py │ └── cli.py └── tests/
三、动态版本
从__init__.py读取版本:
toml
复制
下载
[project] dynamic = ["version"] [tool.setuptools.dynamic] version = {attr = "mypackage.__version__"}python
复制
下载
# src/mypackage/__init__.py __version__ = "1.0.0"
或用setuptools_scm从Git tag:
toml
复制
下载
[build-system] requires = ["setuptools>=61", "setuptools_scm>=8"] [tool.setuptools_scm]
四、构建wheel
bash
复制
下载
pip install build python -m build
生成:
text
复制
下载
dist/ ├── mypackage-1.0.0-py3-none-any.whl └── mypackage-1.0.0.tar.gz
wheel vs sdist
wheel:预编译,安装快
sdist:源码包,需构建
优先发布wheel。
五、安装与测试
bash
复制
下载
# 本地安装 pip install dist/mypackage-1.0.0-py3-none-any.whl # 开发模式 pip install -e . # 测试 pip install -e ".[dev]" pytest
六、CLI入口
python
复制
下载
# src/mypackage/cli.py import argparse def main(): parser = argparse.ArgumentParser() parser.add_argument("name") args = parser.parse_args() print(f"Hello, {args.name}") if __name__ == "__main__": main()pyproject.toml:
toml
复制
下载
[project.scripts] mytool = "mypackage.cli:main"
安装后可执行:
bash
复制
下载
mytool Tom
七、发布到PyPI
7.1 注册账号
PyPI:https://pypi.org
TestPyPI:https://test.pypi.org
7.2 配置token
~/.pypirc:
ini
复制
下载
[distutils] index-servers = pypi testpypi [pypi] username = __token__ password = pypi-xxxxx [testpypi] repository = https://test.pypi.org/legacy/ username = __token__ password = pypi-xxxxx
7.3 上传
bash
复制
下载
pip install twine # 测试 twine upload --repository testpypi dist/* # 正式 twine upload dist/*
八、私有PyPI
8.1 devpi
bash
复制
下载
pip install devpi-server devpi-web devpi-server --start --host localhost --port 3141 devpi use http://localhost:3141 devpi user -c myuser password=xxx devpi login myuser --password=xxx devpi index -c dev # 上传 devpi upload dist/*
8.2 pypiserver
bash
复制
下载
pip install pypiserver pypi-server run -p 8080 -P . -a . ./packages
上传:
bash
复制
下载
twine upload --repository-url http://localhost:8080 dist/*
客户端:
bash
复制
下载
pip install mypackage --index-url http://localhost:8080/simple/
8.3 阿里云/腾讯云制品库
企业推荐用云厂商的制品库,集成权限、审计。
九、CI/CD自动发布
9.1 GitHub Actions
yaml
复制
下载
name: Publish on: release: types: [published] jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.11" - run: pip install build twine - run: python -m build - run: twine upload dist/* env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}9.2 版本号管理
语义化版本MAJOR.MINOR.PATCH:
MAJOR:不兼容变更
MINOR:新功能
PATCH:Bug修复
十、多平台wheel
含C扩展的包需要为每个平台构建wheel。用cibuildwheel:
yaml
复制
下载
- uses: pypa/cibuildwheel@v2.16 env: CIBW_BUILD: cp39-* cp310-* cp311-* CIBW_ARCHS_LINUX: x86_64 CIBW_ARCHS_MACOS: x86_64 arm64
十一、实战:完整打包流程
11.1 项目结构
text
复制
下载
mytool/ ├── pyproject.toml ├── README.md ├── LICENSE ├── src/mytool/ │ ├── __init__.py │ └── cli.py └── tests/test_cli.py
11.2 pyproject.toml
toml
复制
下载
[build-system] requires = ["setuptools>=61"] build-backend = "setuptools.build_meta" [project] name = "mytool" version = "0.1.0" description = "命令行工具示例" readme = "README.md" requires-python = ">=3.9" dependencies = ["click>=8"] [project.scripts] mytool = "mytool.cli:main" [tool.setuptools.packages.find] where = ["src"]
11.3 cli.py
python
复制
下载
import click @click.command() @click.argument("name") def main(name): """向指定用户问好""" click.echo(f"Hello, {name}!") if __name__ == "__main__": main()11.4 构建发布
bash
复制
下载
python -m build twine check dist/* twine upload dist/*
十二、常见问题
12.1 包找不到
检查
[tool.setuptools.packages.find]src布局必须配置where = ["src"]
12.2 数据文件缺失
toml
复制
下载
[tool.setuptools.package-data] mytool = ["data/*.json"]
12.3 版本冲突
用pip check检查依赖。
12.4 平台兼容
用environment markers:
toml
复制
下载
dependencies = [ "pywin32; sys_platform == 'win32'", "uvloop; sys_platform != 'win32'", ]
十三、总结
现代Python打包用pyproject.toml,构建wheel,twine发布。私有包用devpi或pypiserver。CI自动发布提升效率。记住语义化版本和依赖锁定(pip-compile)。