Python 包发布全流程:从项目结构到 PyPI 上线,以及我踩过的那些坑
本文不深入讲解工具本身,而是以我发布的
ratelimited-embedder为例,完整记录一个 Python 包从编写、打包、测试到最终发布到 PyPI 的每一步,并列举常见错误和解决方案,帮你避开那些"坑"。
前言
当你写好一个 Python 工具,想让全世界的人通过 pip install your-package 轻松安装,就需要把它发布到 PyPI(Python Package Index)。这个过程看似简单,实则有不少容易出错的地方。我在发布第一个包时,就踩了 license 格式、版本号不一致、API token 混淆、换行符警告等一堆坑。
本文将按时间顺序,一步步带你走完整个流程,并附上我当时遇到的错误和解决方法。无论你是初学者还是有一定经验的开发者,相信都能从中受益。
一、准备项目结构
推荐使用 src/ 布局,这是现代 Python 打包的最佳实践,可避免开发时意外导入本地代码而非安装的包。
your-package-name/
├── src/
│ └── your_package_name/ # 模块名(与包名尽量一致)
│ ├── __init__.py
│ ├── core.py
│ └── ...
├── tests/ # 单元测试
├── pyproject.toml # 核心配置文件
├── README.md
├── LICENSE # 许可证(如 MIT, Apache 2.0)
└── .gitignore
注意 :包名可以使用连字符(如 ratelimited-embedder),但导入时的模块名必须使用下划线(ratelimited_embedder)。
二、编写 pyproject.toml
这是 Python 打包的"身份证",必须包含以下核心字段:
toml
[build-system]
requires = ["setuptools>=68.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "ratelimited-embedder" # PyPI 上的名称
version = "0.1.1" # 遵循语义化版本
description = "工具简短描述"
readme = "README.md"
license = { text = "Apache-2.0" } # 注意:不是字符串 "Apache-2.0"
authors = [{ name = "Your Name", email = "you@example.com" }]
requires-python = ">=3.9"
dependencies = [
"psutil>=5.9.0",
"tqdm>=4.65.0",
]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
]
[project.urls]
Homepage = "https://github.com/yourname/your-repo"
Repository = "https://github.com/yourname/your-repo"
踩坑记录
- ❌ 错误写法:
license = "MIT"
✅ 正确写法:license = { text = "MIT" }或license = { file = "LICENSE" } - 缺少许可证分类器 :必须在
classifiers中添加License :: OSI Approved :: Apache Software License对应的分类器,否则twine check会警告。 - 版本号不一致 :
pyproject.toml中的version必须与src/your_package/__init__.py中的__version__保持一致。建议使用importlib.metadata动态读取。
三、编写 README 和 LICENSE
- README.md:应包含安装方法、快速示例、核心 API 说明。PyPI 支持 Markdown 渲染,推荐使用。
- LICENSE:在 GitHub 创建仓库时可以直接选择许可证模板(MIT、Apache 2.0 等),然后下载到本地。
注意:README 中的示例代码要确保可运行,且与当前版本 API 一致。
四、本地构建与检查
1. 安装构建工具
bash
pip install build twine
2. 构建分发包
bash
python -m build
成功后会生成 dist/ 目录,内含 .tar.gz 源码包和 .whl wheel 包。
3. 检查元数据
bash
twine check dist/*
如果输出 PASSED,说明元数据基本正确;如果有警告或错误,需根据提示修改 pyproject.toml。
常见错误:
long_description_content_type缺失或格式不对 → 确保 README 后缀为.md,并在pyproject.toml中不设置(默认会读取)。- 缺少许可证分类器 → 添加对应 classifier。
五、在 TestPyPI 上预发布
TestPyPI 是 PyPI 的测试实例,在这里上传包不会影响正式环境,非常适合演练。
1. 注册账号并生成 API token
- 访问 test.pypi.org 注册账号。
- 登录后进入 Account settings → API tokens → Add API token。
- 选择 "Entire account",生成 token(以
pypi-开头),复制保存(只显示一次)。
2. 上传到 TestPyPI
bash
twine upload --repository testpypi dist/*
输入用户名:__token__
密码:粘贴刚才复制的 token。
3. 验证安装
创建一个干净的环境:
bash
python -m venv test_env
source test_env/bin/activate # Windows: test_env\Scripts\activate
pip install --index-url https://test.pypi.org/simple/ your-package-name
然后尝试导入,确认功能正常。
踩坑记录
- Token 搞混 :TestPyPI 的 token 不能用于正式 PyPI,反之亦然。上传前务必确认
--repository参数。 - 包名已存在 :如果 TestPyPI 上已有同名包(哪怕是别人占用的),需要修改
name。一般 TestPyPI 比较宽松,但正式 PyPI 更严格。
六、正式发布到 PyPI
1. 注册正式 PyPI 账号并生成 token
- 访问 pypi.org 注册(不同于 TestPyPI,账号不通用)。
- 同样在 API tokens 中创建一个 token,作用域选 "Entire account"。
2. 上传
bash
twine upload dist/*
用户名:__token__
密码:正式 PyPI 的 token。
上传成功后,访问 https://pypi.org/project/your-package-name/ 就能看到项目页面。
踩坑记录
- 403 Forbidden :常见原因有:
- 使用了 TestPyPI 的 token。
- token 作用域限制为某个项目,而当前项目名不匹配。
- 包名已被人注册(需要改名)。
- 版本号重复 :PyPI 不允许覆盖已发布的版本。如果需要在同一版本上修复,必须递增版本号(如
0.1.1→0.1.2)。永远不要删除已发布的版本。
七、关联 GitHub 与版本标签
为了方便用户查看源码和提 issues,建议将代码同步到 GitHub,并在每次发布时打 tag。
bash
git add .
git commit -m "Release v0.1.1"
git tag v0.1.1
git push origin main --tags
然后在 GitHub 仓库页面创建 Release,附上打包好的 dist/* 文件(可选)。
八、常见问题汇总
| 问题 | 原因 | 解决方案 |
|---|---|---|
twine upload 返回 400 |
pyproject.toml 中许可证字段格式错误 |
改为 license = { text = "MIT" } |
pip install 找不到包 |
未上传到正确的 PyPI 仓库 | 检查是否用了 --index-url 指定 TestPyPI |
twine check 警告 No classifier for license |
classifiers 缺少许可证分类器 |
添加 "License :: OSI Approved :: MIT License" |
安装后导入报 ModuleNotFoundError |
模块名与包名不一致 | 确保 src/ 下的文件夹名与 pyproject.toml 中 name 的连字符转下划线一致 |
Git 提示 LF will be replaced by CRLF |
Windows 换行符转换警告 | 可以忽略,或设置 git config core.autocrlf true |
上传时提示 no such file or directory: 'dist/*' |
没有先执行 python -m build |
先构建分发包 |
| 用户反馈安装后缺少依赖 | dependencies 中遗漏了必备包 |
列出所有运行时依赖,包括间接依赖(如 langchain-core) |
九、更新包版本
当工具功能更新或修复 bug 时,需要发布新版本:
- 修改
pyproject.toml中的version,例如0.2.0。 - 同步修改
src/your_package/__init__.py中的__version__。 - 更新
CHANGELOG.md(推荐)。 - 重新构建:
python -m build - 上传:
twine upload dist/*
PyPI 会自动索引新版本,用户执行 pip install --upgrade your-package 即可升级。
十、总结
发布一个 Python 包并不复杂,但细节繁多。核心步骤可以概括为:
准备项目 → 编写 pyproject.toml → 构建 → TestPyPI 预演 → 正式 PyPI 上传 → GitHub 关联
过程中遇到错误不可怕,关键是要看懂错误信息并善用搜索引擎。希望本文能帮你少走弯路,顺利发布自己的第一个 PyPI 包。
如果觉得有用,欢迎点赞、收藏、转发。有任何问题也可以在评论区交流。