Python 与数据实践 · 第 22 章

构建与发布

到现在为止,你写的每一个工程都只在自己电脑上活得好好的。可是代码的价值,恰恰在于它能跑到别人的环境里去——同事要用、服务器要用、三个月后换了机器的你也要用。这一章就解决一件事:怎么把一坨源码,变成一条命令就能装进任何环境…

约 12 分钟4,734 字12 个代码示例

到现在为止,你写的每一个工程都只在自己电脑上活得好好的。可是代码的价值,恰恰在于它能跑到别人的环境里去——同事要用、服务器要用、三个月后换了机器的你也要用。这一章就解决一件事:怎么把一坨源码,变成一条命令就能装进任何环境的"成品"。

本章配了一个可以动手折腾的示例工程 daylab-toolkit/(目录就放在本章课件旁边),后面所有演示都围绕它展开。


1. 一个库的完整旅程

先在脑子里铺一张地图。一个公共库从出生到被人使用,要走过这样一条流水线:

编写源码 → 填写工程清单 → 本地构建 → 上传仓库 → 他人安装
 (src 布局)  (pyproject.toml)  (生成产物)   (PyPI/私有源)   (pip install)

打个比方:源码好比刚出锅的菜,没法直接快递;构建就是把它做成罐头(统一规格、密封包装);发布是把罐头摆上超市货架(仓库);安装则是顾客下单提货。这一章我们只干"做罐头、上货架"这两件事,全程手动用最原始的工具操作一遍——下一章会有一个把整条流水线一脚油门踩到底的现代工具。

2. pyproject.toml:工程的说明书

构建工具怎么知道你的工程叫什么、依赖什么、代码放在哪?答案全在一个文件里:pyproject.toml。它是工程的"身份证 + 装箱单"。

先看 daylab-toolkit 的完整配置,逐行拆解:

# pyproject.toml

[build-system]                     # 构建环节的声明:告诉前端用哪个后端干活
requires = ["hatchling"]           # 干活前需要先装上的后端工具
build-backend = "hatchling.build"  # 具体调用哪个包的哪个模块来完成构建

[project]                          # 工程的对外名片,发布后所有人都能看到
name = "daylab-toolkit"            # 发行版名称(在 PyPI 上的门牌号)
version = "0.2.1"                  # 版本号,按「主.次.修订」的语义化规则递增
description = "演示公共库从源码到分发包完整链路的教学工程"
requires-python = ">=3.14"         # 声明支持的最低 Python 版本
readme = "README.md"               # 首页说明文件的路径,会展示在仓库页面上
dependencies = [                   # 运行期必需的第三方库及其版本约束
    "pypinyin>=0.53",              # 汉字转拼音
    "python-slugify>=8.0",         # 文本转 URL 友好别名
]

[tool.hatch.build.targets.sdist]   # 源码包(sdist)的打包规则
# 这里留空:默认把工程里的文件全部纳入

[tool.hatch.build.targets.wheel]   # 安装包(wheel)的打包规则
packages = ["src/daylab_toolkit"]  # 指明真正的包在 src 目录下,只打包它

几个容易踩的点提前说清:

  • 两个名字:发行版名 daylab-toolkit(给人类和仓库看,中划线连接)与导入名 daylab_toolkit(给 import 语句看,下划线连接),是一对约定俗成的"双胞胎",别写混了。
  • 版本号不是随便写的:修 bug 升修订位、加功能升次位、破坏兼容才动主位——这是生态默认的默契,遵循语义化版本规范。
  • packages 为什么指向 src/ 里面:这是 src 布局的必然结果。源码藏在 src/ 下可以避免"从工程根目录直接 import 到未安装版本"的糊涂账,但代价就是打包时必须显式告诉后端去哪儿找包。

实用小贴士:在 VSCode 里装一个 Even Better TOML 插件,pyproject.toml 会有语法高亮和字段补全,体验完全不一样。

3. 构建的前台与后台

你可能注意到:构建这件事被拆成了两个角色——

  • 构建前端:负责调度流程的"传菜员",常见选择有 build、uv、Poetry、PDM;
  • 构建后端:真正动手打包的"后厨",常见选择有 hatchling、setuptools、pdm-backend、uv-build、poetry-core。

为什么要拆开?因为打包这件事存在一份社区公认的标准协议:

构建协议:https://peps.python.org/pep-0517/ 产物标准:https://peps.python.org/pep-0427/

只要前后端都遵守协议,就可以任意搭配:用 uv 当前端、hatchling 当后端;明天换成 Poetry 前端、setuptools 后端也行。pyproject.toml 里的 [build-system] 就是在声明这次用哪个组合。

本章为了让你看清底层细节,用最朴素的方式操作:前端用官方的 build,后端用 hatchling。

4. 亲手构建一次

进入 daylab-toolkit 目录,四步走:

# 1. 造一个干净的虚拟环境
python -m venv .venv

# 2. 进入这个环境
source .venv/bin/activate

# 3. 把官方前端工具装进来
pip install build

# 4. 一声令下,开始打包
python -m build

跑完之后,工程里会多出一个 dist/ 目录,里面躺着两件产物:

dist/
├── daylab_toolkit-0.2.1.tar.gz          # sdist:源码包
└── daylab_toolkit-0.2.1-py3-none-any.whl # wheel:安装包

它们俩的区别值得记一辈子:

产物 本质 安装时
sdist(.tar.gz) 源码快照,连测试、文档一起装进去 环境里要先现场"编译组装"一遍
wheel(.whl) 预先组装好的成品,解开就能用 直接落位,装完即走

所以真实世界里 pip 拿到两个包时会优先选 wheel,又快又稳;sdist 则是"源码备胎",保证极端情况下也能从源码装起来。

5. 出货前的质检

产物生成不等于万事大吉。上传之前,用官方工具做个体检:

# 安装发布工具(里面带检查功能)
pip install twine

# 检查产物元数据是否齐全合规
twine check dist/*

它会核对包的名称、版本、描述等字段是否填写规范。仓库那边也会做类似校验,但在上传之前本地先查一遍,能省掉"传上去被打回来"的来回折腾。

6. 发布到官方仓库 PyPI

PyPI(https://pypi.org)是 Python 世界的中央货架,全世界的 pip install 默认都从这里取货。把罐头摆上去的流程:

  1. 注册账号:https://pypi.org/account/register/
  2. 创建访问令牌:https://pypi.org/manage/account/token/ ——不要用账号密码直接上传,令牌更安全,且可以随时吊销
  3. 复制令牌(一串 pypi- 开头的字符)
  4. 上传:
twine upload dist/*
# 按提示粘贴令牌即可

名字是先到先得的:daylab-toolkit 这种名字八成已经被人占了,动手前先去 PyPI 搜一下,占用了就换一个。

上传成功后,任何人(包括另一台机器上的你)都能直接安装:

pip install daylab-toolkit

7. 发布到私有仓库

公司内部往往不想把代码公开上 PyPI,这时候就需要"自家超市"——私有制品仓库。自己从零搭一个相当费事,主流做法是直接用云服务,例如阿里云的云效制品仓库(https://packages.aliyun.com):开通服务、新建一个 Python 类型的仓库,按页面指引把包传上去即可。

私有仓库真正的价值在于团队统一供给:内部公共库 + 外部第三方库,一个入口全搞定。

从私有仓库安装

原理很简单:把 pip 默认取货的地址改成你的仓库。在 pip 的配置文件里(Linux/macOS 是 ~/.pip/pip.conf,Windows 是 %APPDATA%\pip\pip.ini)写入:

[global]
index-url = 私有仓库地址         # 主货架:优先来这里找包
extra-index-url = https://pypi.tuna.tsinghua.edu.cn/simple  # 备用货架:私有仓库没有的去镜像源找
trusted-host = packages.aliyun.com  # 跳过该域名的 HTTPS 校验(仅内网场景建议)

配置生效后,安装体验与从前完全一致:

pip install daylab-toolkit   # 先查私有仓库,查不到再去备用源

8. 可编辑安装:边改边用

发布流程走完,来聊一个日常开发离不开的技巧。

场景很常见:你在写一个公共库 daylab-toolkit,同时另一个工程正在使用它。如果每次改一行库代码都要重新构建、重新安装,一天下来时间全耗在"装了卸、卸了装"上。

可编辑安装(editable install,也叫开发模式安装)专治这个问题:

# 在使用方工程的环境里执行(指向库工程的路径)
pip install -e ../daylab-toolkit

装完之后:

# 任意位置都能导入
from daylab_toolkit import full_pinyin

# 改了库的源码?不用重装,下一次调用自动是新代码

它的原理一点也不神秘:普通安装是把代码复制一份进 site-packages;可编辑安装则是在 site-packages 里放一个 .pth 文件——相当于一张指路牌,指向你工程里的源码目录。解释器每次导入时顺着指路牌找到的是源码本体,所以你怎么改,运行时就用什么。

9. 动手玩示例工程

本章的 daylab-toolkit/ 就在课件同级目录,结构如下:

daylab-toolkit/
├── src/
│   └── daylab_toolkit/       # 包本体(src 布局)
│       ├── pinyin/           # 工具一:汉字转拼音(依赖 pypinyin)
│       ├── slug/             # 工具二:标题转 URL 别名(依赖 python-slugify)
│       ├── hello.py          # 零依赖的小工具:欢迎语
│       └── __init__.py
├── pyproject.toml            # 工程说明与打包配置
└── README.md

它麻雀虽小、五脏俱全:src 布局、双子模块、两个运行时依赖、完整的打包配置——正好把本章所有知识点串起来。建议按第 4 节的步骤对它真实构建一次,再试试第 8 节的可编辑安装,改一行 hello.py 的返回值,看看不重装的情况下效果是否立刻变化。

作业

  1. 挑一个你自己写的小工具(或者直接用 daylab-toolkit),完整走一遍链路:构建出产物 → 本地校验 → 发布到一个私有制品仓库 → 换一个全新的虚拟环境,从私有仓库把它装回来并运行成功。
  2. 合上课件,找一张白纸,凭记忆画出"源码 → 构建 → 发布 → 安装"的全流程图,并向一位同伴(或者对着空气,费曼学习法)讲清楚每个环节产出了什么、为什么要分 sdist 和 wheel 两种产物。讲不圆的地方,就是你还没真正吃透的地方。

下一章:23 · 工程化工具链