PyPI 发布指南:从 0 到 1 发布 Python 包

一、准备工作

1. 注册 PyPI 账号

这是最基础的步骤:

2. 生成 API Token

登录后进入 API Token 管理页面

  • 点击 “Create API token”,填一个 Token name
  • Scope 选择 “Entire account”(或指定项目)
  • 复制生成的 Token(格式:pypi-AgEIcHlwaS5vcmc...

3. 配置认证(推荐)

创建 ~/.pypirc 文件(Windows 为 %USERPROFILE%\.pypirc):

1
2
3
4
5
6
7
8
9
[pypi]
repository = https://upload.pypi.org/legacy/
username = __token__
password = pypi-你的完整Token

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

二、项目配置

1. pyproject.toml(必须)

创建或修改 pyproject.toml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "your-package-name"
version = "0.1.0"
description = "你的包描述"
authors = [
{ name="Your Name", email="your@email.com" }
]
readme = "README.md"
license = "MIT"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
requires-python = ">=3.9"
dependencies = [
# 你的依赖,如 "requests>=2.28.0"
]

[project.urls]
"Homepage" = "https://github.com/your-username/your-repo"

2. MANIFEST.in(可选但推荐)

创建 MANIFEST.in 控制 sdist 包含的文件:

1
2
3
4
5
6
7
8
9
10
11
# 包含的文件
include README.md
include LICENSE
include pyproject.toml

# 包含源码目录
recursive-include your_package_name *.py

# 排除大文件(如二进制)
exclude *.whl
exclude node_modules/

三、构建脚本

我写了一个通用的 build_wheel.py 脚本,可以直接复制使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!/usr/bin/env python3
"""
通用 Python 包构建脚本
支持清理、构建 wheel 和 sdist
"""

import os
import shutil
import subprocess
import argparse


def clean_dist():
"""清理 dist 目录"""
if os.path.exists("dist"):
shutil.rmtree("dist")
print("已清理 dist/ 目录")


def build_wheel():
"""构建 wheel"""
print("开始构建 wheel...")
result = subprocess.run(
["python", "-m", "build", "--wheel"],
capture_output=True,
text=True
)
if result.returncode != 0:
print(f"构建失败:\n{result.stderr}")
exit(1)
print("wheel 构建成功")


def build_sdist():
"""构建 sdist"""
print("开始构建 sdist...")
result = subprocess.run(
["python", "-m", "build", "--sdist"],
capture_output=True,
text=True
)
if result.returncode != 0:
print(f"sdist 构建失败:\n{result.stderr}")
exit(1)
print("sdist 构建成功")


def show_dist():
"""显示构建产物"""
if os.path.exists("dist"):
print("\n构建产物:")
for f in os.listdir("dist"):
fpath = os.path.join("dist", f)
size = os.path.getsize(fpath) / (1024 * 1024)
print(f" - {f} ({size:.1f} MB)")


def main():
parser = argparse.ArgumentParser(description="构建 Python 包")
parser.add_argument(
"--clean", action="store_true", help="先清理 dist 目录"
)
parser.add_argument(
"--wheel-only", action="store_true", help="只构建 wheel"
)
args = parser.parse_args()

# 检查 build 是否安装
try:
import build
except ImportError:
print("安装 build 工具...")
subprocess.run(["pip", "install", "build"], check=True)

if args.clean:
clean_dist()

build_wheel()

if not args.wheel_only:
build_sdist()

show_dist()


if __name__ == "__main__":
main()

保存为 tools/build_wheel.py,使用方式:

1
2
3
4
5
# 清理并构建
python tools/build_wheel.py --clean

# 只构建 wheel(避免 sdist 过大问题)
python tools/build_wheel.py --clean --wheel-only

四、发布流程

1. 安装上传工具

1
pip install twine

2. 构建包

1
python tools/build_wheel.py --clean --wheel-only

3. 上传到 PyPI

1
2
3
4
5
# 使用 .pypirc 配置(推荐)
twine upload dist/*

# 或命令行指定 Token
twine upload dist/* -u __token__ -p pypi-你的Token

4. 验证发布

访问 https://pypi.org/project/your-package-name/ 查看结果。


五、多平台构建

如果需要支持多个平台,可以使用 cibuildwheel

安装

1
pip install cibuildwheel

配置 pyproject.toml

1
2
3
[tool.cibuildwheel]
build = "cp39-* cp310-* cp311-*"
skip = ["*-musllinux_*"]

构建所有平台

1
cibuildwheel --output-dir wheelhouse

上传多平台 wheel

1
twine upload wheelhouse/*.whl

六、完整发布清单

每次发布新版本时按这个顺序来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1. 更新版本号(pyproject.toml)

# 2. 更新 CHANGELOG.md(推荐)

# 3. 提交代码
git add .
git commit -m "Release v0.1.0"
git tag v0.1.0

# 4. 构建
python tools/build_wheel.py --clean --wheel-only

# 5. 上传
twine upload dist/*.whl

# 6. 推送到 GitHub
git push origin main
git push origin v0.1.0

七、FAQ

Q: 上传时报错 403 Forbidden?

  1. 邮箱未验证 → 登录 PyPI 验证邮箱
  2. Token 错误 → 重新生成 Token
  3. 用户名错误 → 必须是 __token__,不是账号名

Q: 上传时报错 400 File too large?

  • 默认单文件限制 100 MB
  • 解决方案:只上传 wheel(sdist 通常不需要)

Q: 如何测试发布?

使用 TestPyPI:

1
2
twine upload --repository testpypi dist/*.whl
pip install your-package --index-url https://test.pypi.org/simple/

Q: wheel 和 sdist 的区别?

  • wheel:预编译二进制包,安装快,跨平台(带平台标记)
  • sdist:源码包,用户安装时需要编译,通常较小

总结

发布 Python 包并不复杂,关键步骤:

  1. 配置好 pyproject.toml
  2. 写个构建脚本方便复用
  3. 使用 twine 上传
  4. 遇到问题优先查 PyPI 官方文档