PEP 771 – Python 软件包的默认附加功能
- 作者:
- Thomas Robitaille <thomas.robitaille at gmail.com>,Jonathan Dekhtiar <jonathan at dekhtiar.com>
- 发起人:
- Pradyun Gedam <pradyunsg at gmail.com>
- 讨论至:
- Discourse 帖子
- 状态:
- 草案
- 类型:
- 标准跟踪
- 主题:
- 打包
- 创建日期:
- 2025年1月13日
- 发布历史:
- 2025年1月15日,2025年2月6日,2025年6月9日
摘要
PEP 508 指定了一种用于声明软件包依赖项的迷你语言。该语言的一个特性是能够指定**附加功能 (extras)**,它们是分发包的可选组件,使用时会安装额外的依赖项。本 PEP 提出了一种机制,允许在未明确提供任何附加功能时,默认安装一个或多个附加功能。
动机
本 PEP 中默认附加功能的各种用例和可能的解决方案在此 DPO 帖子中进行了广泛讨论。这些用例可分为两大类,为本 PEP 提供了动机。
推荐但非必需的依赖项
软件包维护者通常使用附加功能来声明可选依赖项,这些依赖项可扩展软件包的功能或性能。在某些情况下,可能很难确定哪些依赖项是必需的,哪些应归类为附加功能。必须在典型用户(可能更喜欢默认提供大多数功能)和需要最小安装且不包含大型可选依赖项的用户之间取得平衡。现有 Python 打包基础设施的一种解决方案是,软件包维护者定义一个名为(例如)recommended
的附加功能,其中包含所有非必需但建议的依赖项。然后指示用户使用 package[recommended]
安装软件包,而那些喜欢更多控制的用户可以使用 package
。然而,在实践中,许多用户不知道 [recommended]
语法,这使得他们在典型安装时需要知道这一点。如果有一种方法可以默认安装推荐的依赖项,同时提供一种方式供用户请求更小的安装,那么这将满足此用例,本 PEP 描述了此解决方案。
通过鼓励用户默认包含额外依赖项来演示此模式的软件包示例包括:
支持多个后端或前端的软件包
使用附加功能的另一个常见用例是定义不同的后端或前端以及每个后端或前端需要安装的依赖项。软件包可能需要安装至少一个后端或前端才能正常运行,但可能对哪个后端或前端是灵活的。此类前端或后端的具体示例包括:
根据当前的打包标准,维护者要么必须要求其中一个后端或前端,要么要求用户始终指定附加功能,例如 package[backend]
,因此如果用户只安装 package
,则存在无法使用的安装风险。如果有一种方法可以指定一个或多个默认后端或前端,并提供一种方法来覆盖这些默认值,这将为用户提供更好的体验,本 PEP 中描述的方法将允许这样做。
请注意,本 PEP 不旨在解决禁止冲突或不兼容的附加功能的问题,例如,如果一个软件包需要恰好一个前端或后端软件包。目前在 Python 打包基础设施中没有机制来禁止安装冲突或不兼容的附加功能,本 PEP 不会改变这一点。
需要至少一个后端或前端才能工作并推荐默认附加功能以安装后端或前端的软件包示例包括:
在所有这三种情况下,安装不带任何附加功能的软件包会导致安装失败,这对于其中一些软件包来说是一个常见的支持问题。
基本原理
社区多年来广泛讨论了许多可能的解决方案,包括在此 DPO 帖子以及许多问题和拉取请求中。下面提出的解决方案:
这是所有讨论过的解决方案中唯一一个满足所有三个标准的解决方案。
规范
Default-Extra
元数据字段
一个新的多用途元数据字段 Default-Extra
将添加到核心软件包元数据中。对于此字段,每个条目必须是一个字符串,指定一个附加功能,该附加功能将在未明确指定任何附加功能的情况下安装软件包时自动包含。
只有已在 Provides-Extra 条目中指定的条目才能在 Default-Extra
条目中使用。
示例
Default-Extra: recommended
Default-Extra: backend1
Default-Extra: backend2
Default-Extra: backend3
由于这在核心软件包元数据中引入了一个新字段,因此这将需要将Metadata-Version提升到下一个次要版本(撰写本文时为 2.5)。
[project]
元数据表中的新键
一个新的键将被添加到 PEP 621 中最初定义并在PyPA 规范中定义的 [project]
元数据表中。此键将命名为 default-optional-dependency-keys
,描述如下:
- TOML 类型:字符串数组
- 相应的核心元数据字段:
Default-Extra
default-optional-dependency-keys
中的每个字符串都必须是在可选依赖项中定义的附加功能的名称,并且此数组中的每个附加功能都将转换为核心软件包元数据中匹配的 Default-Extra
条目。将生成上一节中显示的示例 Default-Extra
条目的有效用法示例是:
[project]
default-optional-dependency-keys = [
"recommended",
]
和
[project]
default-optional-dependency-keys = [
"backend1",
"backend2",
"backend3"
]
覆盖默认附加功能
如果在依赖项规范中明确给出了附加功能,则忽略默认附加功能。否则,将安装默认附加功能。
例如,如果一个软件包定义了 extra1
默认附加功能以及一个非默认的 extra2
附加功能,那么如果用户使用以下命令安装软件包:
$ pip install package
将包含默认的 extra1
依赖项。如果用户改为使用以下命令安装软件包:
$ pip install package[extra2]
则将安装 extra2
附加功能,但将忽略默认的 extra1
附加功能。
如果在安装命令或依赖项树中多次指定了同一个软件包,则如果任何软件包实例未指定附加功能,则必须安装默认附加功能。例如,如果安装软件包 spam
,其中 package
在依赖项树中多次出现:
spam
├── tomato
│ ├── package[extra2]
└── egg
└── package
则应安装默认附加功能,因为 package
至少出现一次且未指定附加功能。
空附加功能集,例如 package[]
,应解释为表示应**不**安装任何默认附加功能(除非 package
出现在依赖项树的其他位置,在这种情况下,将**安装**默认附加功能,如上所述)。这将提供一种获取软件包最小安装的通用方法。
我们还注意到,某些工具(例如 pip)目前会忽略无法识别的附加功能,并向用户发出警告以指示附加功能未被识别,例如:
$ pip install package[non-existent-extra]
WARNING: package 3.0.0 does not provide the extra 'non-existent-extra'
...
对于表现如此(而不是引发错误)的工具,如果在依赖项规范中识别出附加功能无效,则应忽略它;如果所有指定的附加功能都无效,则应将其视为等同于 package[]
(而不是 package
),并且**不**安装任何默认附加功能。
最后,我们注意到(正如依赖工具来取消选择任何默认附加功能中讨论的那样),软件包安装器可以实现自己的选项来控制上述行为,例如实现一个选项,禁用某些或所有软件包的默认附加功能,无论这些软件包在依赖项树中的位置如何。如果实现了此类工具特定的选项,工具开发人员应使其可选,并且用户应将上述 PEP 771 行为作为默认行为。
示例
本节我们将研究动机一节中描述的用例,以及如何通过使用上面概述的规范来解决这些用例。
推荐的依赖项和最小安装
首先,我们考虑默认安装推荐但非严格必需的依赖项,同时提供仅安装必需依赖项的方法。
为此,软件包维护者将定义一个名为 recommended
的附加功能,其中包含推荐但非必需的依赖项,并选择将其作为默认附加功能包含:
[project]
default-optional-dependency-keys = [
"recommended"
]
[project.optional-dependencies]
recommended = [
"package1",
"package2"
]
如果此软件包名为 package
,则安装 package
的用户将获得与 package[recommended]
等效的安装。用户也可以选择安装 package[]
,这将安装不带默认附加功能的软件包。
以动机一节中的一个具体软件包示例为例,astropy 软件包定义了一个 recommended
附加功能,用户目前被指示在默认安装说明中安装它。有了这个 PEP,recommended
附加功能可以声明为默认附加功能:
[project]
default-optional-dependency-keys = [
"recommended"
]
[project.optional-dependencies]
recommended = [
"scipy",
"..."
]
这意味着安装:
$ pip install astropy
然后将安装可选但推荐的依赖项,例如 scipy。想要最小安装的高级用户可以使用:
$ pip install astropy[]
需要至少一个后端或前端的软件包
如动机中所述,某些软件包可能支持多个后端和/或前端,在某些情况下,可能需要确保始终至少安装一个后端或前端软件包,否则软件包将无法使用。具体示例可能包括需要 GUI 库才能使用的 GUI 应用程序,但能够支持不同的 GUI 库,或者依赖于不同计算后端但需要至少安装一个的软件包。
在这种情况下,软件包维护者可以选择为每个后端或前端定义一个附加功能,并提供一个默认值,例如:
[project]
default-optional-dependency-keys = [
"backend1"
]
[project.optional-dependencies]
backend1 = [
"package1",
"package2"
]
backend2 = [
"package3"
]
如果软件包可以同时支持例如多个后端,并且其中一些后端应该始终安装,那么这些后端的依赖项必须作为必需的依赖项提供,而不是使用默认附加功能机制。
以动机中提到的一个具体示例为例,napari 软件包可以使用 PyQt5、PyQt6、PySide2 或 PySide6 中的一个,用户目前需要明确指定 napari[all]
才能安装其中一个,或者例如 napari[pyqt5]
来明确指定其中一个前端软件包。安装不带任何附加功能的 napari
会导致软件包无法运行。有了这个 PEP,napari
可以定义以下配置:
[project]
default-optional-dependency-keys = [
"pyqt5"
]
[project.optional-dependencies]
pyqt5 = [
"PyQt5",
"..."
]
pyside2 = [
"PySide2",
"..."
]
pyqt6 = [
"PyQt6",
"..."
]
pyside6 = [
"PySide6",
"..."
]
这意味着:
$ pip install napari
将开箱即用,但仍然会有一种机制供用户明确指定前端,例如:
$ pip install napari[pyside6]
支持不应删除默认附加功能的附加功能
我们在此考虑的另一个情况是,软件包维护者希望支持用户选择非默认附加功能,而不删除默认附加功能。本质上,他们可能希望:
package[]
提供不带任何附加功能的安装package
安装推荐的依赖项(在recommended
附加功能中)package[alternative]
不安装默认附加功能,但安装一组备选的可选依赖项(在alternative
附加功能中)package[additional]
同时安装推荐和附加依赖项(在additional
附加功能中)
这可以通过例如以下方式实现:
[project]
default-optional-dependency-keys = [
"recommended"
]
[project.optional-dependencies]
recommended = [
"package1",
"package2"
]
alternative = [
"package3"
]
additional = [
"package[recommended]",
"package4"
]
软件包在附加功能中引用自身的功能得到了现有 Python 打包工具的支持。
再次考虑一个具体示例,通过此 PEP,astropy 可以定义一个 recommended
附加功能(如推荐依赖项和最小安装中所述)。但是,它还定义了其他附加功能,例如 jupyter
,它添加了增强 Jupyter 环境中用户体验的软件包。选择此附加功能的用户可能仍然希望安装推荐的依赖项。在这种情况下,以下配置将解决此问题:
[project]
default-optional-dependency-keys = [
"recommended"
]
[project.optional-dependencies]
recommended = [
"scipy",
"..."
]
jupyter = [
"astropy[recommended]",
"ipywidgets",
"..."
]
用户安装:
$ pip install astropy[jupyter]
然后将得到与以下内容相同的结果:
$ pip install astropy[recommended, jupyter]
具有多种默认值的软件包
在某些情况下,软件包可能需要多种默认设置。例如,在需要至少一个后端或前端的软件包中,我们考虑了只有后端或前端的软件包的情况,但在某些情况下,软件包可能必须同时支持后端和前端,并且希望指定一个或多个默认前端和一个或多个默认后端。
理想情况下,人们可能希望以下行为:
$ pip install package # installs default backend and frontend
$ pip install package[] # installs no backends or frontends
$ pip install package[backend1] # installs backend1 and default frontend
$ pip install package[frontend2] # installs frontend2 and default backend
$ pip install package[backend1, frontend2] # installs backend1 and frontend2
然而,本 PEP 选择不提供一种机制,使其在指定 backend1
时,默认后端被禁用,但默认前端被启用,因为这会增加复杂性。
维护者目前应记录,如果明确指定了后端或前端,则需要同时指定后端和前端。对于想要这样做但却发现问题的用户来说,可发现性应该不是问题,因为用户在任何情况下都需要阅读文档才能找出可用的后端或前端,因此他们可以同时了解如何正确使用后端和前端的附加功能。
一种提高用户友好性的选择是,维护者可以创建名为 defaultbackend
和 defaultfrontend
的附加功能,它们确实安装了默认后端和前端。然后他们可以建议用户这样做:
$ pip install package # installs default backend and frontend
$ pip install package[] # installs no backends or frontends
$ pip install package[backend1, defaultfrontend] # installs backend1 and default frontend
$ pip install package[defaultbackend, frontend2] # installs frontend2 and default backend
$ pip install package[backend1, frontend2] # installs backend1 and frontend2
这将允许(如果需要)用户获得任何推荐的后端,即使该默认值随时间变化。
如果将来有希望实施更好的解决方案,我们相信本 PEP 不应排除这一点。例如,将来可以想象添加一项功能,允许附加功能指定它禁用了**哪些**默认附加功能,如果未指定此功能,则明确指定的附加功能将禁用所有默认附加功能(与本 PEP 一致)。
向后兼容性
不使用默认附加功能的软件包
一旦打包生态系统中的工具添加了对本 PEP 的支持,不使用默认附加功能的软件包将继续按原样工作,并且不应出现兼容性问题。
使用默认附加功能的软件包
一旦软件包开始定义默认附加功能,这些默认值将仅在实现本 PEP 的最新版本打包工具中生效,但这些软件包仍可使用旧版打包工具进行安装——主要区别在于,在使用旧版打包工具时,默认附加功能不会自动安装。
如如何教授中所述,软件包作者需要根据其用户群仔细评估何时以及如何采用默认附加功能,因为某些操作(例如将必需依赖项移动到默认附加功能)可能会导致使用旧版软件包安装程序(不支持默认附加功能)的用户出现故障。从这个意义上讲,软件包作者应注意,此功能如果以某些方式使用,可能会给用户带来向后兼容性问题,因此他们有责任确保将对用户的影响降至最低。
安全隐患
本 PEP 没有已知的安全隐患。
如何教授
本节概述了与本 PEP 实施相关的应提供给社区中不同群体的信息。下面描述的一些方面甚至在 PEP 完全在打包工具中实施之前就已相关,因为可以在此实施之前进行一些准备工作,以促进未来的任何潜在过渡。下面涵盖的群体有:
软件包最终用户
应向软件包用户提供清晰的安装说明,其中显示软件包可用的附加功能及其行为方式,例如解释默认情况下将安装哪些推荐依赖项或给定的前端或后端,以及如何根据可用情况选择退出或覆盖默认值。
打包仓库维护者
需要考虑对为不同发行版(如 conda、Homebrew、Linux 软件包安装器(如 apt
和 yum
)等)重新打包 Python 库的个人产生的影响。并非所有软件包发行版都有与所描述的方法一致的机制。事实上,某些发行版(如 conda)甚至没有附加功能的概念。
这里有两种情况需要考虑:
- 在手动重新打包的情况下,例如一些 conda-forge 配方,特别是当没有等效的附加功能时,默认附加功能的引入不应产生很大影响,因为已经必须手动决定包含哪些依赖项(例如,动机中提到的 astropy 软件包的 conda-forge 配方默认包含所有
recommended
依赖项,因为用户无法以其他方式明确请求它们)。 - 在以自动化方式进行重新打包的情况下,发行版维护者将需要仔细考虑如何处理默认附加功能,这可能意味着大量的工作和讨论。
像这样的 PEP 不可能详尽地考虑每种不同的软件包发行版。然而,最终,默认附加功能应被理解为软件包作者希望其软件包为大多数用户安装的方式,这应指导关于如何处理默认附加功能的决策,无论是手动还是自动。
参考实现
以下仓库包含一个功能齐全的演示软件包,该软件包使用默认附加功能:
https://github.com/wheel-next/pep_771
这使用了多个软件包的修改分支,以下链接指向这些分支:
上述实现目前是概念验证,现有更改尚未经过相关维护者的审查。尽管如此,它们功能足以让感兴趣的维护者进行尝试。
被拒绝的想法
使用元软件包进行推荐安装
使用现有的打包工具和基础设施,希望为某些用户提供最小安装,并为普通用户提供默认非最小安装(例如,包含推荐依赖项或默认后端)的软件包维护者,如果他们愿意分发两个软件包而不是一个软件包,从技术上讲已经可以实现这一点——例如 package-core
将是具有最小依赖项的主软件包,而 package
将是依赖于 package-core
并启用了可选依赖项的元软件包。
再次以动机一节中的一个具体示例为例,astropy 软件包定义了一个 recommended
附加功能,用户目前被指示在默认安装说明中安装它。原则上,可以将现有的 astropy
软件包重命名为例如 astropy-core
,然后创建一个新的 astropy
软件包,它将是一个元软件包,包含以下依赖项部分:
dependencies = [
"astropy-core[recommended]"
]
由于用户可能希望对 astropy
元软件包设置版本或版本约束(例如 astropy>5.0
),因此元软件包需要遵循与核心软件包相同的版本,并且需要在依赖项部分使用严格的版本固定,例如:
version = "7.1.0"
dependencies = [
"astropy-core[recommended]==7.1.0"
]
这个想法看起来很有吸引力,因为它在技术上已经可行。然而,在实践中,许多项目出于多种原因选择不这样做,我们现在将一一探讨。其中一些可能不适用于未来的新项目,但其中一些适用于所有项目,无论新旧。
软件包与模块名称不匹配
在命名方面,希望使用元软件包方法的软件包有两种主要选择:
- 第一个选择是保留现有软件包不变,这意味着
package
将提供最小安装,然后创建一个具有不同名称的新元软件包,例如package-all
。然而,这会遇到最初促使本 PEP 产生的问题之一——用户通常不知道他们可以执行例如package[recommended]
,因此同样地,他们可能不会意识到package-all
的存在。这再次将发现的负担放在了普通用户身上,而不是将部分负担转移到高级用户身上。 - 第二种选择是将现有软件包重命名为例如
package-core
,并将新的元软件包命名为package
。这比第一种选择更好,但并不理想,因为它引入了软件包名称和模块名称之间不直观的不匹配,即package-core
提供package
模块,而package
不提供任何模块。一个导致混淆的例子是,普通用户可能会认为卸载package
模块会通过例如以下方式完成:$ pip uninstall package
但事实并非如此(
package
模块仍然可以工作),而且这个用户可能不清楚package-core
软件包甚至存在。
多个仓库或单一仓库
这种方法需要维护两个仓库而不是一个,或者改用包含两个软件包的单一仓库。这两种选择都不是理想的:
- 分成两个仓库会给维护者带来长期的额外负担,他们必须确保这两个仓库保持同步(在版本方面以及附加功能等方面,如同步元数据中所述)。此外,软件包和模块名称不匹配中提到的命名问题在这里还有其他复杂性——要么仓库名称与软件包匹配,在这种情况下,任何拥有以前
package
仓库签出的用户都需要更新其远程 URL 或任何 git 克隆 URL 以指向package-core
仓库。另一种选择是保留package
仓库以包含package-core
软件包,并为元软件包使用不同的名称,但这可能导致混淆。 - 切换到单一仓库对于某些项目来说可能是一个重大变化,而且工具通常默认假定单个仓库对应单个软件包——虽然这些工具通常可以配置为然后使用单一仓库,但这会给维护者带来额外负担。此外,如果主软件包移动到单一仓库的子目录中,任何从仓库 URL 安装软件包的用户(例如 pip 安装)都需要调整此操作以从子目录安装(在仓库 URL 中添加
subdirectory=
),并且任何克隆仓库并假定以前布局的现有工作流都将中断。
依赖于最小软件包
需要依赖早于拆分首次发生的软件包版本的软件包,将无法轻松依赖最小软件包。然而,在本 PEP 的主要提议中,下游用户将能够依赖例如 package[]>version
,其中 version
早于默认附加功能的引入,而采用拆分方法,下游用户将无法依赖例如 package-core>version
,因为 package-core
以前不存在。
对此的一种可能解决方案是开发人员为软件包的所有旧版本发布无操作元数据软件包,但这会给维护者带来巨大的额外负担。
卸载
正如在软件包与模块名称不匹配中提到命名问题时所暗示的,卸载软件包将不再按用户预期的方式工作。用户执行:
$ pip uninstall package
仍会留下 package-core
,但可能没有意识到。这不仅令人困惑,而且实际上是一种破坏性更改,可能会影响许多现有工作流。
软件包分发
用两个软件包代替一个软件包会增加软件包分发的长期维护成本,仅仅因为必须发布两个软件包而不是一个,在某些情况下,这会在每次发布时引入额外的体力劳动。
同步元数据
在主软件包和元软件包之间保持同步的主要元数据是版本。每当核心软件包发布新版本时,元软件包都需要更新其版本以及依赖项中核心软件包的版本固定。
此外,核心软件包中定义的所有附加功能都需要在元软件包中重新定义并保持同步。例如,如果 package
定义了一个 additional
附加功能,用户应该仍然能够安装 package[additional]
,但安装 package-core
软件包的用户也应该可以选择执行 package-core[additional]
。
需要保持同步的其他元数据包括例如作者信息和项目 URL。
总结
总的来说,这个解决方案将意味着更高的维护负担,不仅在初始设置和过渡方面(对于大型成熟项目来说可能已经令人望而却步),而且在长期维护方面也是如此。这还有可能破坏用户工作流(特别是在仓库和例如卸载问题上)。出于所有这些原因,我们不认为它是一个有说服力的替代方案。
取消选择附加功能的语法
主要的竞争方法之一如下:如果明确提供了任何附加功能,则不取消选择默认值,而是需要明确取消选择默认附加功能。
在这种情况下,将引入一种新的取消选择附加功能的语法,作为 PEP 508 中定义的迷你语言的扩展。如果一个软件包定义了默认附加功能,用户可以通过在附加功能名称前使用减号 (-
) 来选择退出这些默认值。提议的语法更新如下:
extras_list = (-)?identifier (wsp* ',' wsp* (-)?identifier)*
此新语法的有效示例包括,例如:
package[-recommended]
package[-backend1, backend2]
package[pdf, -svg]
然而,这种方法存在两个主要问题:
- 需要定义一些规则来解释边缘情况,例如附加功能及其否定版本同时出现在同一依赖项规范中(例如
package[pdf, -pdf]
)或者如果依赖项树同时包含package[pdf]
和package[-pdf]
,而且这些规则对用户来说并不直观。 - 更重要的是,这将向依赖项规范引入新语法,这意味着如果任何软件包使用新语法定义了依赖项,它以及任何其他依赖于它的软件包将不再能通过现有打包工具安装,因此这将是一个主要的向后兼容性中断。
由于这些原因,此替代方案未包含在最终提案中。
在 extras_require
中添加一个特殊条目
一种潜在的解决方案,作为引入新的 Default-Extra
元数据字段的替代方案,是利用一个具有“特殊”名称的附加功能。
一个例子是使用一个空字符串:
Provides-Extra:
Requires-Dist: numpy ; extra == ''
其思想是,作为“空”附加功能一部分安装的依赖项只有在未指定其他附加功能时才会被安装。在 https://github.com/pypa/setuptools/pull/1503 中提出了一个实现,但发现没有办法在不破坏现有用法兼容性的情况下使其工作。例如,通过 setup.py
文件使用 Setuptools 的软件包可以执行:
setup(
...
extras_require={'': ['package_a']},
)
这是有效的,并且等同于在 install_requires
中定义 package_a
,因此更改空字符串的含义将破坏兼容性。
此外,不能使用其他字符串(例如 'default'
)作为特殊字符串,因为所有将是向后兼容的有效附加功能名称的字符串可能已经在现有软件包中使用。
有人建议使用特殊的 Python 变量 None
,但这再次是不可能的,因为尽管可以在 setup.py
文件中使用 None
,但在声明性文件(例如 setup.cfg
或 pyproject.toml
)中则不行,而且最终附加功能名称必须转换为软件包元数据中的字符串。拥有:
Provides-Extra: None
将与字符串“None”无法区分,而“None”可能已经作为 Python 软件包中的附加功能名称使用。如果我们修改核心元数据语法以允许非字符串的“特殊”附加功能名称,那么我们又回到了修改核心元数据规范,这并不比引入 Default-Extra
更好。
依赖工具来取消选择任何默认附加功能
取消选择附加功能的另一种选择是在打包工具层面实现。例如,pip 可以包含一个选项,例如:
$ pip install package --no-default-extras
此选项可以应用于所有或特定软件包,类似于 --no-binary
选项,例如:
$ pip install package --no-default-extras :all:
这种方法的优点是支持默认附加功能的工具也可以支持取消选择它们。这种方法将类似于 apt
工具的 --no-install-recommends
选项。
然而,这种解决方案本身并不理想,因为它不允许软件包自身指定它们不需要依赖项的一些默认附加功能。它还会给用户带来风险,如果用户在大型依赖树中禁用所有默认附加功能,可能会破坏依赖于默认附加功能的树中的软件包。
尽管如此,本 PEP 并不禁止这种方法,并且由不同打包工具的维护者决定是否要支持此类选项。它是一个至少对软件包维护者有用的标志,他们希望识别依赖树中依赖默认附加功能的位置。然而,如果它受支持,则应明确指出使用此标志不能保证功能正常的环境。
版权
本文档置于公共领域或 CC0-1.0-Universal 许可证下,以更宽松者为准。
来源:https://github.com/python/peps/blob/main/peps/pep-0771.rst