Python 打包分发:setup.py 与 PyPI 发布
将你开发的 Python 包发布到 PyPI(Python Package Index),让全球开发者都能通过 pip install 安装使用,这是开源贡献的重要方式。本文将手把手带你完成整个打包与发布流程。
一、为什么需要打包发布
当你开发了一个功能模块,希望他人能够便捷地安装使用时,直接让对方克隆仓库、添加到 PYTHONPATH 的方式过于繁琐。通过标准化的打包发布流程,用户只需一行命令即可完成安装,同时你也能获得版本管理、依赖解决等完整支持。
PyPI 是 Python 官方的软件仓库,类似于 npm 之于 JavaScript、maven 之于 Java。发布到 PyPI 后,你的包可以被全世界的 Python 开发者发现和使用。
二、前期准备工作
2.1 注册 PyPI 账号
访问 https://pypi.org 注册一个账号。记住你的用户名和密码,后续上传时需要用到。
需要注意的是,PyPI 有两个站点:
建议先在测试仓库练习,确认流程无误后再发布到正式仓库。
2.2 安装必要工具
pip install setuptools wheel twine
setuptools:Python 包构建的核心工具wheel:二进制分发格式,比源码包安装更快twine:安全上传包到 PyPI 的工具
三、项目目录结构
一个规范的 Python 包应该具有以下结构:
my_package/
├── my_package/ # 包目录(与包名同名)
│ ├── __init__.py # 包初始化文件
│ ├── module.py # 模块文件
│ └── ...
├── tests/ # 测试目录(可选)
│ └── test_*.py
├── setup.py # 构建配置文件
├── setup.cfg # 附加配置(可选)
├── pyproject.toml # 现代配置方式(可选)
├── README.md # 项目说明
├── LICENSE # 许可证文件
└── requirements.txt # 依赖列表(可选)
关键点:__init__.py 文件的存在告诉 Python 这是一个包,可以为空文件,也可以包含包的版本信息和导入语句。
四、编写 setup.py
setup.py 是 Python 包构建的核心配置文件,定义了包的元数据和构建方式。
4.1 基础配置
from setuptools import setup, find_packages
setup(
name="my_package",
version="0.1.0",
author="Your Name",
author_email="your.email@example.com",
description="A short description of the package",
long_description=open("README.md").read(),
long_description_content_type="text/markdown",
url="https://github.com/yourusername/my_package",
packages=find_packages(),
classifiers=[
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
],
python_requires=">=3.8",
install_requires=[
"requests>=2.25.0",
"numpy>=1.20.0",
],
)
4.2 关键参数说明
| 参数 | 作用 | 必填 |
|---|---|---|
name |
包的唯一标识名 | 是 |
version |
版本号,遵循语义化版本规范 | 是 |
packages |
需要包含的包列表 | 是 |
install_requires |
运行依赖列表 | 否 |
python_requires |
兼容的 Python 版本 | 否 |
classifiers |
包的分类信息,帮助用户筛选 | 否 |
find_packages() 函数会自动发现当前目录下的所有包,无需手动列出。如果你的项目结构简单,也可以直接使用 packages=["my_package"]。
4.3 添加可执行脚本
如果你的包包含命令行工具,可以通过 entry_points 参数添加:
setup(
# ... 其他参数
entry_points={
"console_scripts": [
"mycli=my_package.cli:main",
],
},
)
这样安装后,用户可以直接在终端使用 mycli 命令,它会调用 my_package.cli 模块中的 main 函数。
五、构建分发包
5.1 生成分发文件
在项目根目录下执行:
python setup.py sdist bdist_wheel
这条命令会生成两种分发格式:
- 源码分发包(sdist):
.tar.gz文件,包含原始源代码 - 二进制分发包(bdist_wheel):
.whl文件,预编译的二进制格式,安装更快
执行完成后,项目目录会新增两个文件夹:
dist/:包含生成的分发文件build/:构建过程中的临时文件*.egg-info/:元数据信息
5.2 验证分发文件
使用 twine 工具检查包的结构是否正确:
twine check dist/*
如果检测通过,会显示 "Passed"; 如果发现问题,会给出具体的修复建议。
六、上传到 PyPI
6.1 先上传到测试仓库(推荐)
twine upload --repository testpypi dist/*
系统会提示输入用户名和密码。输入你在 https://test.pypi.org 注册的账号信息。
上传成功后,可以安装测试验证:
pip install --index-url https://test.pypi.org/simple/ my_package
6.2 上传到正式仓库
测试确认无误后,执行:
twine upload dist/*
输入正式 PyPI 的用户名和密码。发布成功后,就可以在 https://pypi.org 上搜索到你的包了。
七、版本更新与维护
当需要发布新版本时,按照以下步骤操作:
- 修改版本号:编辑
setup.py中的version参数 - 更新日志:在
CHANGELOG.md或README.md中记录变更内容 - 重新构建:删除旧的
dist/目录,重新执行python setup.py sdist bdist_wheel - 上传新版本:
twine upload dist/*
PyPI 不允许同一版本号重复上传。如果上传失败,检查是否忘记更新版本号。
八、常见问题与解决方案
8.1 "HTTPError: 400 Bad Request" 上传失败
最常见的原因是版本号重复。修改 setup.py 中的版本号后重新构建上传。
8.2 包导入失败
检查 packages 参数是否正确包含了所有子包。如果使用 find_packages() 后仍然有问题,可以显式列出:packages=["my_package", "my_package.submodule"]。
8.3 依赖未正确安装
确保 install_requires 列表中的依赖名称正确。可以临时创建一个虚拟环境测试:python -m venv test_env && source test_env/bin/activate && pip install your_package。
九、PyPI 项目页面配置
发布成功后,在 PyPI 项目页面完善以下信息:
| 设置项 | 作用 |
|---|---|
| Project description | 显示在搜索结果中的摘要 |
| Project URL | 指向 GitHub、文档等链接 |
| Author | 作者信息 |
| License | 开源许可证类型 |
| Classifiers | 选择适当的分类标签 |
这些信息帮助用户更好地发现和理解你的包。
十、使用 pyproject.toml(现代方式)
Python 3.12+ 和最新版本的 setuptools 支持使用 pyproject.toml 作为配置文件:
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "my_package"
version = "0.1.0"
description = "A short description"
readme = "README.md"
requires-python = ">=3.8"
authors = [
{ name = "Your Name", email = "your.email@example.com" }
]
dependencies = [
"requests>=2.25.0",
]
[project.scripts]
mycli = "my_package.cli:main"
这种方式的优点是配置更简洁,标准化程度更高。

暂无评论,快来抢沙发吧!