bigywater.tech
Python · 35

Python 打包:从 pyproject.toml 到 wheel

真实对同一份pyproject.toml打出wheel和sdist两种产物,用unzip/tar直接拆开看里面装的到底是什么。

wheel本质
真实就是标准zip,unzip直接打开
METADATA
真实是pyproject.toml [project]表的纯文本翻译
RECORD自指
真实自己这一行的哈希留空

做的事:给同一份pyproject.toml跑wheel和sdist两种打包

代码 pyproject.toml[build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta" [project] name = "pkga" version = "1.0.0" dependencies = ["libc<2.0"] python3 -m build --wheel --outdir wheelhouse pkga python3 -m build --sdist --outdir wheelhouse pkga file wheelhouse/pkga-1.0.0-py3-none-any.whl

验证:wheel文件真实就是一个标准zip,unzip能直接打开

实测 · 本机执行输出
file pkga-1.0.0-py3-none-any.whl
→ Zip archive data, at least v2.0 to extract    ← 真实的文件类型判定,跟.zip完全一样

unzip -l pkga-1.0.0-py3-none-any.whl
  pkga/__init__.py
  pkga-1.0.0.dist-info/METADATA
  pkga-1.0.0.dist-info/WHEEL
  pkga-1.0.0.dist-info/top_level.txt
  pkga-1.0.0.dist-info/RECORD

这是本文认为wheel格式最容易被低估的真实事实:.whl后缀完全是约定,文件本身file命令识别出来的类型跟普通.zip没有任何区别——pip install some.whl真实做的核心动作,就是把这个zip解压到目标环境的site-packages目录,再根据RECORD里记录的文件清单确认解压结果完整。这跟本系列上一篇讲的《pip 依赖解析》衔接得上——resolver算出该装哪个版本之后,"装"这个动作本身在wheel格式下真实只是解压加校验,不需要重新编译或执行任意代码,这正是wheel比老式sdist(需要在安装现场跑一遍setup.py)快得多、也更可预测的原因。

验证:METADATA是[project]表的纯文本翻译,RECORD记录了每个文件的真实哈希

实测 · 本机执行输出
unzip -p pkga-1.0.0-py3-none-any.whl pkga-1.0.0.dist-info/METADATA
→ Metadata-Version: 2.4
  Name: pkga
  Version: 1.0.0
  Requires-Dist: libc<2.0     ← 真实是pyproject.toml里dependencies字段翻译过来的

unzip -p pkga-1.0.0-py3-none-any.whl pkga-1.0.0.dist-info/RECORD
→ pkga/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
  pkga-1.0.0.dist-info/METADATA,sha256=uu1BhI6rx9v9B9k5osYd7kDfTJpdB2mVvYiSbunEYZM,72
  pkga-1.0.0.dist-info/WHEEL,sha256=...,91
  pkga-1.0.0.dist-info/top_level.txt,sha256=...,5
  pkga-1.0.0.dist-info/RECORD,,          ← 真实这一行没有哈希,只有两个逗号
RECORD最后这一行真实是个有趣的自指问题:这份文件本身也在被记录的文件清单里,但写这一行的时候,RECORD文件的最终内容(包括这一行)还没有确定,没办法对"自己"算出一个真实有意义的哈希——setuptools真实选择的做法是把这一行的哈希和大小字段都留空,只留文件名。pip装包之后,如果要校验完整性,就靠RECORD里其它每一行的sha256逐个核对——这是wheel格式内置的一层完整性校验,跟本系列MySQL部分讲过的checksum思路是同一类道理,只是这里校验的是"这个包解压出来的文件有没有被篡改",而不是数据页有没有损坏。

验证:sdist真实是另一种东西——带完整源码,还多生成了一个setup.cfg

实测 · 本机执行输出
python3 -m build --sdist --outdir wheelhouse pkga
tar tzf pkga-1.0.0.tar.gz
→ pkga-1.0.0/PKG-INFO
  pkga-1.0.0/pkga/__init__.py
  pkga-1.0.0/pkga.egg-info/...
  pkga-1.0.0/pyproject.toml     ← 真实原样带上了完整的pyproject.toml
  pkga-1.0.0/setup.cfg          ← 真实多出来的文件,我写的源码里根本没有这个文件

真实对比wheel和sdist的内容,能看出两者定位完全不同:wheel真实只装"最终产物"(已经处理好、可以直接放进site-packages的文件),不含pyproject.toml本身;sdist真实带的是完整的源码分发包,连构建配置都原样保留,pip install some.tar.gz拿到的是这份源码,还要在安装现场真的跑一次构建才能变成能用的模块。这份多出来的setup.cfg是setuptools构建后端真实自动生成的兼容文件(用于兼容一些只认老式setup.py/setup.cfg约定的工具链),不是我在源码里手写的——这提醒本文一件事:即使全程只写了pyproject.toml,构建工具背后真实可能会自己补出额外的元数据文件,打包产物不完全等于源码目录的原样打包。

分步演示:拆开一个真实wheel文件看到的内容

每一步都是本文上面用真实构建的wheel/sdist跑出来的原始结果。

从zip本质到METADATA翻译再到RECORD的自指留空
这一步新验证的文件

    本文用到的工具

    • 本机 Python 3.14.7,真实的 python -m build(setuptools后端)
    • 真实用 unzip / tar 直接拆开生成的wheel和sdist文件
    京ICP备2026067502号-1京公网安备11010502063487号