Python 包发布全流程:从项目结构到 PyPI 上线,以及我踩过的那些坑

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 settingsAPI tokensAdd 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.10.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.tomlname 的连字符转下划线一致
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 时,需要发布新版本:

  1. 修改 pyproject.toml 中的 version,例如 0.2.0
  2. 同步修改 src/your_package/__init__.py 中的 __version__
  3. 更新 CHANGELOG.md(推荐)。
  4. 重新构建:python -m build
  5. 上传:twine upload dist/*

PyPI 会自动索引新版本,用户执行 pip install --upgrade your-package 即可升级。


十、总结

发布一个 Python 包并不复杂,但细节繁多。核心步骤可以概括为:

准备项目 → 编写 pyproject.toml → 构建 → TestPyPI 预演 → 正式 PyPI 上传 → GitHub 关联

过程中遇到错误不可怕,关键是要看懂错误信息并善用搜索引擎。希望本文能帮你少走弯路,顺利发布自己的第一个 PyPI 包。

如果觉得有用,欢迎点赞、收藏、转发。有任何问题也可以在评论区交流。

文中示例工具 ratelimited-embedder 地址:GitHub | PyPI

相关推荐
lbb 小魔仙1 小时前
Python + LangChain 环境搭建完全指南:从零构建本地 RAG 知识库(附 Ollama 本地模型集成)
开发语言·python·langchain
xxjj998a1 小时前
PHP vs C#:两大编程语言终极对比
开发语言·c#·php
Lenyiin1 小时前
《LeetCode 顺序刷题》61 - 70
java·c++·python·算法·leetcode·lenyiin
岁岁的O泡奶1 小时前
NSSCTF_crypto_[LitCTF 2023]babyLCG
经验分享·python·算法·密码学·crypto·流密码
风落无尘1 小时前
我用 LangChain 写了一个带“定速巡航”的向量化工具,发布到 PyPI 了!
人工智能·python·langchain
AI技术控1 小时前
RAG 效果差不是模型问题:10 个检索增强失败原因总结
人工智能·python·自然语言处理
敲代码的瓦龙2 小时前
Android?基础UI控件!!!
java·开发语言
Hesionberger2 小时前
LeetCode 78:子集生成全攻略
java·开发语言·数据结构·python·算法·leetcode·职场和发展
bzmK1DTbd2 小时前
Swagger API文档:Java RESTful服务的自动生成
java·开发语言·restful