登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  python教程

Python wheel 构建元数据和依赖声明

来源:17golang原创

时间:2026-10-17 08:13:05 182浏览 收藏

Python wheel 的依赖声明应放在 [project].dependencies,构建后端及其依赖放在 [build-system],项目版本则使用静态 version 或由后端提供的动态版本。三者解决不同问题:能生成 wheel,不代表安装它时就会得到运行所需的库。

Python 打包官方指南:https://packaging.python.org/en/latest/guides/writing-pyproject-toml/

Setuptools 官方文档:https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html;build 官方文档:https://build.pypa.io/en/stable/。

从“开发机正常,安装包后缺依赖”说起

设想一个名为 parcel-note 的内部工具:源码中的 parcel_note 包需要 packaging 解析版本。开发机早已安装 packaging,于是导入正常;交付 wheel 后,同事在另一套环境中却遇到缺少该库的问题。这里是用于解释配置的原创示例场景,不是真实事故记录,也不提供实测日志。

处理这个问题时,最容易出现的误判是“wheel 文件坏了”。实际上应先区分:包自身没被包含,还是包已经安装但依赖没有被声明。本文只处理后者,以及与它相邻的构建后端、版本来源问题,不讨论扩展编译或包索引上传。

把配置变更串起来,才能看见触发条件

这个示例的变化顺序很简单:工具起初直接在开发环境运行;后来加入 pyproject.toml 并生成 wheel;最后把 wheel 交给其他环境使用。出问题的触发条件不是“换了机器”本身,而是新环境不再替项目提前安装缺失依赖。

假如配置只在 build-system.requires 中加入 packaging,它表达的是构建时需要这个库,不等于声明安装后的运行依赖。反过来,只把 setuptools 写入运行依赖,也不能正确替代后端声明。build 是构建前端,负责调用后端;示例后端是 setuptools.build_meta,两者不是同一个角色。参见 build 的职责说明。

pyproject 中构建配置与项目依赖的静态分组结构
结构说明图:构建区包含 build-system.requires 与 build-backend,项目区包含 dependencies 与 optional-dependencies;连线表达归属,不代表执行顺序。

根因是把构建依赖误当成运行依赖

先给这个示例设置一个明确目录约定:根目录放 pyproject.toml,Python 包位于 src/parcel_note/,其中已有 __init__.py。下面配置适用于这一约定;项目名与版本号仅为示例,并非要求读者注册或安装某个同名公开项目。

[build-system]
# 这一组只负责构建所需工具;版本下限沿用官方当前示例
requires = ["setuptools>=77.0.3"]
build-backend = "setuptools.build_meta"

[project]
name = "parcel-note"
version = "0.4.0"
description = "An internal parcel note helper"
# 这是本示例自行约定的支持范围,不是 wheel 格式的最低要求
requires-python = ">=3.11"
# packaging 是工具运行需要的库,应在这里声明
dependencies = ["packaging>=24"]

[project.optional-dependencies]
# 只有选择 cli 功能时才需要 rich;版本范围需结合实际支持测试决定
cli = ["rich>=13"]

[tool.setuptools.packages.find]
# 显式使用 src 布局,避免让示例依赖自动发现的隐含假设
where = ["src"]

dependencies 是安装该项目通常需要的依赖,optional-dependencies 则按功能分组。cli 在这里是 extra 的名字,不会因为写在文件中就成为所有用户的必装依赖。库名和版本范围要来自实际代码需求与兼容性测试,不能把开发机中的全部包照搬进去。字段用法见 PyPA 依赖声明指南。

再强调两个限制:运行依赖通常不会作为完整第三方库直接塞入 wheel;requires-python 也只是兼容性声明,不会自动把新语法转换成旧版本语法。把下限写低不能使代码获得兼容能力。

版本只保留一个权威来源

示例先采用静态 version,目的在于把故障范围缩小。它是 parcel-note 自身的发布版本,与 setuptools 的版本、packaging 的依赖版本不是一回事。三种数字不能相互替换,也不能仅通过重命名 wheel 文件来更改项目元数据。

如果团队已经维护包内版本常量,可以改用后端支持的动态读取。对于 version 这种标量字段,应删除静态 version,再在 project 中声明 dynamic,并为 setuptools 配置读取位置。以下仅展示替换部分,原有 name、dependencies 等配置仍需保留:

[project]
name = "parcel-note"
# 用这一项替代静态 version,不要同时保留 version = "0.4.0"
dynamic = ["version"]

[tool.setuptools.dynamic]
# 由 setuptools 从指定属性取得项目版本
version = {attr = "parcel_note.__version__"}

对应的 src/parcel_note/__init__.py 可以只保留一个简单字符串常量:

# 使用可静态解析的值,避免版本读取依赖网络或运行环境
__version__ = "0.4.0"

不要让版本表达式执行复杂计算,也不要在包初始化中为读取版本强行导入尚未安装的运行库。动态字段如何取得值取决于后端,不能以为写了 dynamic 就自动获得 Git 标签版本。上述 attr 用法由 Setuptools 动态元数据文档支持;其他后端需要使用自己的配置。

项目声明字段与 wheel 核心元数据的对应关系
结构说明图:version、dependencies、requires-python 分别对应 Version、Requires-Dist、Requires-Python;图示是字段映射,不是某次构建产物截图。

修复落在声明层,而不是要求用户手工补包

回到示例,修复动作是补上 project.dependencies,明确后端配置,并决定项目版本的单一来源。如果临时让同事手工安装 packaging,可能解除当次导入错误,却没有修好交付契约;下一个空环境仍可能重复遇到问题。

读者可在自己的项目根目录使用以下构建命令。本文未运行这些命令,不声称生成了任何实际产物:

# 在已准备好 build 的项目环境中,仅构建 wheel
python -m build --wheel

build 会调用配置的构建后端。常规隔离构建不能依赖开发机偶然已有的库;如果后端或插件自身缺失,应处理构建依赖,而不是把它们全部塞进运行依赖。不要为了暂时绕过缺失声明就把关闭隔离当作根治办法。

配置项表达什么不表达什么
build-system.requires构建工具及插件依赖最终用户的完整运行依赖
project.dependencies运行依赖约束完整可复现环境锁定
project.version本项目版本Python 解释器或构建工具版本
project.requires-python解释器版本兼容范围自动测试或语法降级

下一次交付前,应该保留哪些判断依据

在团队自己的交付流程中,可将“配置声明”与“产物实际记录”分别留档:wheel 中 .dist-info/METADATA 的 Version 应体现项目版本,Requires-Dist 描述依赖,Requires-Python 描述解释器范围。不要只凭 wheel 文件存在就宣布依赖问题已经解决。字段含义可参考 核心元数据规范。

如果声明已经正确,安装仍缺依赖,再考虑安装命令是否禁止了依赖解析、是否选择了所需 extra,以及当前环境是否符合约束。不要在没有安装记录时直接断言是 pip 缓存或网络造成的。

requirements.txt 会自动成为 wheel 的依赖吗?

不能这样假定。只有后端的明确配置把某个文件作为元数据输入时,文件内容才可能参与依赖生成。普通 requirements 文件的用途和项目元数据不同,本文采用显式 project.dependencies,避免隐式关联。

把所有依赖写成等号固定版本就一定更稳吗?

不是。项目发布声明约束的是可接受的依赖范围,某个部署环境的精确版本集合是另一层管理问题。是否精确固定,应结合应用交付方式与兼容策略决定,不能拿开发机快照代替项目依赖设计。

换一个构建后端能直接修好缺依赖吗?

仅更换名字通常不能。需要确认新后端理解所用元数据格式、拥有匹配的构建配置,并且依赖声明本身正确。对这个示例而言,先修清楚“构建需要什么、运行需要什么、项目版本从哪里来”,比更换整套工具更直接。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>