PEP 722 – 单文件脚本的依赖项规范
- 作者:
- Paul Moore <p.f.moore at gmail.com>
- PEP 代理:
- Brett Cannon <brett at python.org>
- 讨论对象:
- Discourse 主题
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 主题:
- 打包
- 创建:
- 2023 年 7 月 19 日
- 发布历史:
- 2023 年 7 月 19 日
- 决议:
- Discourse 主题
摘要
本 PEP 规定了一种在单文件 Python 脚本中包含第三方依赖项的格式。
动机
并非所有 Python 代码都以“项目”的形式构建,例如拥有自己的目录,并包含一个 pyproject.toml
文件,并被构建为可安装的分布包。 Python 也经常用作脚本语言,Python 脚本作为(更好的)替代方案来替代 shell 脚本、批处理文件等。当用作创建脚本时,Python 代码通常存储为单个文件,通常位于专门用于此类“实用程序脚本”的目录中,这些目录可能包含多种语言,Python 只是其中一种。这些脚本可能被共享,通常通过电子邮件或链接到 URL(例如 Github gist)等简单方式进行共享。但是,它们通常不会作为正常工作流程的一部分“分发”或“安装”。
以这种方式将 Python 用作脚本语言时,一个问题是如何在包含脚本所需的所有第三方依赖项的环境中运行脚本。目前还没有标准工具来解决这个问题,本 PEP不试图定义一个。但是,任何确实解决这个问题的工具都需要知道脚本所需的第三方依赖项。通过定义一种存储此类数据的标准格式,现有的工具以及任何未来的工具都能够获取这些信息,而无需用户在脚本中包含特定于工具的元数据。
基本原理
由于关键需求是编写单文件脚本,并通过简单地提供脚本副本进行共享,因此本 PEP 定义了一种将依赖项数据嵌入脚本本身中,而不是嵌入外部文件中的机制。
我们定义了依赖项块的概念,其中包含有关脚本依赖的哪些第三方包的信息。
为了识别依赖项块,可以简单地将脚本作为文本文件读取。这是有意为之,因为 Python 语法会随着时间的推移而发生变化,因此试图将脚本解析为 Python 代码将需要选择特定的 Python 语法版本。此外,至少某些工具可能不会用 Python 编写,而期望它们实现 Python 解析器负担太重了。
但是,为了避免需要修改核心 Python,该格式的设计旨在对 Python 解析器显示为注释。可以编写代码,其中依赖项块不会被解释为注释(例如,通过将其嵌入 Python 多行字符串中),但这种用法不鼓励,并且可以轻松避免,假设你没有故意试图创建病态的示例。
对其他语言如何允许脚本指定其依赖项的回顾表明,这种“结构化注释”是一种常用的方法。
规范
本节内容将发布在 Python 打包用户指南的 PyPA 规范部分,作为标题为“在脚本文件中嵌入元数据”的文档。
任何 Python 脚本都可能包含一个依赖项块。依赖项块是通过以文本文件的形式读取脚本(即,该文件不被解析为 Python 源代码)来识别的,查找以下形式的第一行
# Script Dependencies:
哈希字符必须位于行首,前面没有空格。文本“Script Dependencies”不区分大小写,空格表示任意空格(尽管必须至少有一个空格存在)。以下正则表达式识别依赖项块标题行
(?i)^#\s+script\s+dependencies:\s*$
读取依赖项块的工具可以尊重标准 Python 编码声明。如果它们选择不这样做,则必须将文件处理为 UTF-8。
在标题行之后,文件中的所有行,直到第一行不以 #
符号开头,都被认为是依赖项行,并按以下方式处理
- 初始
#
符号将被剥离。 - 如果该行包含字符序列“ # “(空格 哈希 空格),则这些字符以及任何后续字符将被丢弃。这允许依赖项块包含内联注释。
- 剩余文本开头和结尾的空格将被丢弃。
- 如果该行现在为空,则忽略它。
- 该行的内容必须是一个有效的 PEP 508 依赖项规范。
在 #
前后的空格要求是必要的,以便将其与 PEP 508 URL 规范的一部分区分开来(该规范可以包含哈希,但没有周围的空格)。
消费者必须验证至少所有依赖项都以 PEP 508 中定义的 name
开头,并且它们可以验证所有依赖项是否完全符合 PEP 508。如果它们找到无效的规范,则必须失败并显示错误。
示例
以下是一个包含嵌入式依赖项块的脚本示例
# In order to run, this script needs the following 3rd party libraries
#
# Script Dependencies:
# requests
# rich # Needed for the output
#
# # Not needed - just to show that fragments in URLs do not
# # get treated as comments
# pip @ https://github.com/pypa/pip/archive/1.3.1.zip#sha1=da9234ee9982d4bbb3c72346a6de940a148ea686
import requests
from rich.pretty import pprint
resp = requests.get("https://peps.pythonlang.cn/api/peps.json")
data = resp.json()
pprint([(k, v["title"]) for k, v in data.items()][:10])
向后兼容性
由于依赖项块采用结构化注释的形式,因此可以在不改变现有代码含义的情况下添加它们。
可能已经存在与依赖项块形式匹配的注释。虽然标识标题文本“Script Dependencies”是为了最大程度地降低这种风险,但仍然有可能。
在极少数情况下,现有注释会被错误地解释为依赖项块时,可以通过在代码中更早的位置添加实际的依赖项块(如果脚本没有依赖项,则可以为空)来解决此问题。
安全隐患
如果使用自动安装依赖项的工具运行包含依赖项块的脚本,这可能会导致在用户环境中下载并安装任意代码。
这里的风险是使用来运行脚本的工具的功能的一部分,因此应由工具本身解决。本 PEP 引入的唯一额外风险是,如果运行了包含依赖项块的不可信脚本,则可能会安装潜在的恶意依赖项。通过在运行代码之前查看代码,可以解决此风险。
如何教授此内容
该格式旨在接近开发人员可能已经在说明性注释中指定脚本依赖项的方式。所需的结构故意保持最少,以便格式化规则易于学习。
用户需要知道如何编写 Python 依赖项规范。这由 PEP 508 涵盖,但对于简单的示例(预计是经验不足的用户的主要情况),语法要么只是包名称,要么是名称和版本限制,这是一种相当容易理解的语法。
用户还将知道如何使用解释依赖项数据的工具运行脚本。本 PEP 没有涵盖这一点,因为这是此类工具的责任,负责记录如何使用它。
请注意,核心 Python 解释器不解释依赖项块。这对于初学者来说可能是一个令人困惑的地方,他们试图运行 python some_script.py
,并且不明白为什么它失败了。然而,这与目前现状没有什么不同,目前,在没有其依赖项的情况下运行脚本会导致错误。
一般来说,假设如果初学者得到一个包含依赖项的脚本(无论它们是否在依赖项块中指定),提供脚本的人应该解释如何运行该脚本,如果这涉及使用脚本运行程序工具,则应注意这一点。
建议
本节是非规范性的,只是描述了使用依赖项块时的“最佳实践”。
虽然允许工具对需求进行最少验证,但实际上,它们应该尽可能地进行“健全性检查”验证,即使它们无法对 PEP 508 语法进行完全检查。这有助于确保尽早报告未正确终止的依赖项块。在仅检查需求以名称开头以及完全 PEP 508 验证之间的良好折衷方案是检查裸名称,或者检查名称后跟可选的空格,然后是 [
(额外)、@
(urlspec)、;
(标记)或 (<!=>~
(版本)之一。
脚本通常应将依赖项块放置在文件顶部,紧接在任何 shebang 行之后,或者紧接在脚本文档字符串之后。特别是,依赖项块应始终放置在文件中的任何可执行代码之前。这使得人类读者可以轻松找到它。
参考实现
在 Python 中实现此提案的代码相当简单,因此可以在这里包含参考实现。
import re
import tokenize
from packaging.requirements import Requirement
DEPENDENCY_BLOCK_MARKER = r"(?i)^#\s+script\s+dependencies:\s*$"
def read_dependency_block(filename):
# Use the tokenize module to handle any encoding declaration.
with tokenize.open(filename) as f:
# Skip lines until we reach a dependency block (OR EOF).
for line in f:
if re.match(DEPENDENCY_BLOCK_MARKER, line):
break
# Read dependency lines until we hit a line that doesn't
# start with #, or we are at EOF.
for line in f:
if not line.startswith("#"):
break
# Remove comments. An inline comment is introduced by
# a hash, which must be preceded and followed by a
# space.
line = line[1:].split(" # ", maxsplit=1)[0]
line = line.strip()
# Ignore empty lines
if not line:
continue
# Try to convert to a requirement. This will raise
# an error if the line is not a PEP 508 requirement
yield Requirement(line)
被拒绝的想法
为什么不包含其他元数据?
本提案解决的核心用例是识别独立脚本成功运行所需的依赖项。这是一个常见的问题,目前由脚本运行器工具通过特定于实现的方式存储数据来解决。标准化存储格式通过避免将脚本绑定到特定运行器来提高互操作性。
虽然可以争论其他形式的元数据在独立脚本中可能有用,但目前这种需求很大程度上是理论上的。实际上,脚本要么不使用其他元数据,要么将它们存储在现有、广泛使用(因此实际上是标准)的格式中。例如,需要 README 风格文本的脚本通常使用标准的 Python 模块文档字符串,而想要声明版本的脚本则采用常见的约定,即拥有一个 __version__
变量。
在关于此 PEP 的讨论中提出的一种情况是,能够通过类比于包的 Requires-Python
核心元数据项,声明脚本运行所需的最小 Python 版本。与包不同,脚本通常只由一个用户或在一个环境中运行,在这些环境中,多个版本的 Python 并不常见。因此,在脚本的情况下,对这种元数据的需求并不那么关键。作为进一步的证据,目前可用的两个主要脚本运行器,pipx
和 pip-run
,都没有提供在脚本中包含此数据的方法。
创建标准的“元数据容器”格式将统一各种方法,但在实践中,实际上没有真正需要统一,而这种破坏要么会延迟采用,要么更可能只是意味着脚本作者会忽略该标准。
因此,本提案选择只关注存在明确需求且没有现有标准或普遍做法的用例。
为什么不使用每行一个标记?
除了使用带有标题的注释块之外,另一种可能性是在每一行使用标记,例如
# Script-Dependency: requests
# Script-Dependency: click
虽然这使得单独解析行更容易,但它也有一些问题。第一个问题仅仅是它过于冗长,可读性较差。这显然受所选关键字的影响,但所有建议的选项(在作者看来)都比块注释形式的可读性差。
更重要的是,这种形式设计上使得无法要求依赖项规范器全部集中在一个块中。因此,人类读者无法在不仔细检查整个文件的情况下,确定他们是否已识别所有依赖项。有关此问题的进一步讨论,请参见下面的问题:“为什么不允许使用多个依赖项块并合并它们?”
最后,正如参考实现所证明的那样,解析“注释块”形式实际上并不比解析这种形式困难多少。
为什么不使用一种独特的注释形式来表示依赖项块?
本提案的早期版本使用 ##
来识别依赖项块。然而,不幸的是,flake8 linter 实现了一条规则,要求注释在初始 #
符号之后必须留有空格。虽然 PEP 作者认为该规则是错误的,但它是默认开启的,因此在遇到依赖项块时会导致检查失败。
此外,black
格式化器,虽然允许 ##
形式,但它确实会在大多数其他形式的注释之后添加一个空格。这意味着,如果我们选择一个像 #%
这样的替代方案,自动重新格式化将破坏依赖项块。包含空格的形式,如 # #
是可能的,但对普通用户来说不太自然(省略空格是一个明显的错误)。
虽然有可能更改 linter 和格式化器以识别新的标准,但拥有专用前缀的好处似乎不足以弥补过渡成本或用户可能使用旧工具的风险。
为什么不允许使用多个依赖项块并合并它们?
因为它太容易让人类读者忽略存在第二个依赖项块的事实。这可能只会导致脚本运行器意外下载额外的包,甚至可能成为将恶意包偷偷塞进用户机器的一种方式(通过将第二个依赖项块“隐藏”在脚本的主体中)。
虽然“不要运行不受信任的代码”的原则在这里适用,但好处不足以值得冒险。
为什么不使用更标准的数据格式(例如 TOML)?
首先,对替代格式的唯一实用选择是 TOML。Python 打包已经将 TOML 标准化为结构化数据,而使用其他格式,例如 YAML 或 JSON,将毫无必要地增加复杂性和混乱。
所以问题实质上是,“为什么不使用 TOML?”
“依赖项块”格式背后的关键思想是定义一个在脚本中自然读取为注释的内容。依赖项数据对工具和人类读者都有用,因此使用人类可读的格式是有益的。另一方面,TOML 必然有自己的语法,这会分散对底层数据的注意力。
重要的是要记住,编写 Python 脚本的开发人员通常不熟悉 Python 或 Python 打包。他们通常是系统管理员或数据分析师,他们可能只是将 Python 作为一种“更好的批处理文件”来使用。对于此类用户来说,TOML 格式极有可能不熟悉,语法对他们来说会很模糊,而且并不直观。此类开发人员可能只是从 Stack Overflow 等来源复制依赖项规范器,而并没有真正理解它们。将这样的需求嵌入 TOML 结构是一个额外的复杂性——重要的是要记住,这里的目标是让此类用户轻松使用第三方库。
此外,TOML 本质上是一种灵活的格式,旨在支持非常通用的数据结构。用它编写一个简单的字符串列表有很多方法,而且对于没有经验的用户来说,他们不清楚应该使用哪种形式。
另一个潜在的问题是,使用通用 TOML 解析器可能会在某些情况下导致可衡量的性能开销。启动时间通常被认为是运行小型脚本时的一个问题,因此对于那些追求高性能的脚本运行器来说,这可能是一个问题。
最后,将有一些工具预期写入依赖项数据到脚本中——例如,一个 IDE 具有一个功能,当您引用一个库函数时,它会自动添加一个导入和一个依赖项规范器。虽然存在允许编辑 TOML 数据的库,但它们并不总是擅长保留用户的布局。即使存在能有效完成此工作的库,要求所有工具都使用这样的库,对支持此 PEP 的代码来说是一个相当大的负担。
通过选择一个简单的、基于行的格式,没有引号规则,依赖项数据易于阅读(对人和工具)和写入。该格式没有像 TOML 那样灵活,但用例根本不需要这种灵活性。
为什么不使用(可能受限的)Python 语法?
这通常会涉及将依赖项存储为一个(运行时)列表变量,该变量具有一个传统的名称,例如
__requires__ = [
"requests",
"click",
]
其他建议包括静态多行字符串,或将依赖项包含在脚本的文档字符串中。
此提案最显著的问题是它要求所有依赖项数据的消费者都实现一个 Python 解析器。即使语法受到限制,脚本的其余部分将使用完整的 Python 语法,而试图定义一个可以在与周围代码隔离的情况下成功解析的语法,很可能会非常困难并且容易出错。
此外,Python 的语法在每个版本中都会发生变化。如果提取依赖项数据需要一个 Python 解析器,则解析器将需要知道脚本是用哪个版本的 Python 编写的,而对于一个通用的工具来说,拥有一个能够处理多个 Python 版本的解析器,其开销是不可持续的。
即使可以解决上述问题,该格式也会给人一种可以在运行时修改数据的印象。然而,一般情况下并非如此,尝试这样做的代码将遇到意外且令人困惑的行为。
最后,没有证据表明在运行时提供依赖项数据有什么实际用途。如果发现了这种用途,可以通过解析源代码来轻松获取数据——read_dependency_block(__file__)
。
不过值得注意的是,pip-run
实用程序确实实现了(扩展形式的)这种方法。有关pip-run
设计的进一步讨论,可以在该项目的 issue 跟踪器上找到。
为什么不将 pyproject.toml
文件嵌入脚本中?
首先,pyproject.toml
是一种基于 TOML 的格式,因此所有之前关于 TOML 作为格式的担忧都适用。然而,pyproject.toml
是 Python 打包使用的标准,而重复使用现有标准是一个合理的建议,应该根据其自身优点来解决。
第一个问题是,该建议很少意味着所有pyproject.toml
都要支持脚本。脚本不打算被“构建”成任何可分发的工件,例如 wheel(有关这一点的更多内容,请参见下面的内容),因此 pyproject.toml
的 [build-system]
部分几乎没有意义。虽然 pyproject.toml
中的特定于工具的部分可能对脚本有用,但并不清楚像 ruff 这样的工具是否希望以这种方式支持每个文件的配置,这会导致用户预期它能够工作,但它却没有工作时出现混乱。此外,这种特定于工具的配置对于大型项目中的单个文件同样有用,因此我们必须考虑将一个 pyproject.toml
嵌入到一个具有自身 pyproject.toml
的大型项目中的单个文件意味着什么。
此外,pyproject.toml
目前专注于要构建成 wheel 的项目。有关如何将 pyproject.toml
用于不打算构建为 wheel 的项目的正在进行的讨论,在该问题得到解决之前(这可能需要一些它自己的 PEP),似乎过早地讨论将 pyproject.toml
嵌入到脚本中,而脚本绝对不打算以这种方式构建和分发。
因此,结论是(在某些情况下已经明确说明了,但并非所有情况下都明确说明了),本提案意在意味着我们将嵌入 pyproject.toml
的一部分。通常,这将是来自PEP 621 的 [project]
部分,甚至仅仅是该部分中的 dependencies
项。
目前,第一个问题是,如果将提议定义为“嵌入 pyproject.toml
”,我们将鼓励之前段落中讨论的那种混淆——开发者会期望 pyproject.toml
的全部功能,并在遇到差异和限制时感到困惑。因此,最好将这个建议视为仅使用嵌入式 TOML 格式的提案,但具体而言是重用 pyproject.toml
的特定部分的结构。问题就变成了我们如何描述这个结构,而不会让熟悉 pyproject.toml
的人感到困惑。如果我们用 pyproject.toml
来描述它,那么联系仍然存在。但如果我们孤立地描述它,人们会对这个“类似但不同”的结构感到困惑。
同样重要的是要记住,这个提案的目标受众中一个关键部分是那些仅仅将 Python 作为“更好的批处理文件”解决方案的开发者。这些开发者通常不熟悉 Python 包及其约定,并且往往是对打包解决方案的“复杂性”和“难度”最具批判性的人。因此,基于这些现有解决方案的提议可能不受该受众的欢迎,并且很容易导致人们继续使用现有的临时解决方案,而忽略了旨在简化他们生活的标准。
为什么不从导入语句推断需求?
思路是自动识别源文件中的 import
语句,并将它们转换为依赖项列表。
然而,由于以下几个原因,这是不可行的。首先,上面关于必须保持语法易于解析(适用于所有 Python 版本,以及用其他语言编写的工具)的观点在此处同样适用。
其次,符合简单存储库 API 的 PyPI 和其他包存储库没有提供从导入的模块名称解析包名称的机制(另见 相关讨论)。
第三,即使存储库提供了此信息,相同的导入名称也可能对应于 PyPI 上的多个包。有人可能会反对说,只有在多个项目提供相同导入名称的情况下才需要区分需要哪个包。但是,这会让任何人很容易无意中或恶意地破坏工作脚本,方法是将一个包上传到 PyPI,该包提供的导入名称与现有项目相同。另一种选择是,在候选包中选择第一个注册到索引的包,这在以下情况下会令人困惑:一个流行的包用与现有默默无闻的包相同的导入名称进行开发,甚至在以下情况下有害:现有包是恶意软件,故意上传时使用了一个足够通用的导入名称,该名称有很高的被重复使用的可能性。
另一个相关的想法是,将依赖项作为注释附加到导入语句,而不是将它们收集到一个块中,语法如下:
import numpy as np # requires: numpy
import rich # requires: rich
这仍然存在解析问题。此外,在多行导入的情况下将注释放在哪里是模棱两可的,而且可能看起来很丑陋。
from PyQt5.QtWidgets import (
QCheckBox, QComboBox, QDialog, QDialogButtonBox,
QGridLayout, QLabel, QSpinBox, QTextEdit
) # requires: PyQt5
此外,这种语法在所有情况下都无法按预期直观地运行。考虑一下:
import platform
if platform.system() == "Windows":
import pywin32 # requires: pywin32
这里,用户的意思是该包仅在 Windows 上需要,但脚本运行器无法理解这一点(正确的方法是 requires: pywin32 ; sys_platform == 'win32'
)。
(感谢 Jean Abou-Samra 对这一点的清晰讨论)
为什么不简单地在运行时管理环境?
运行具有依赖关系的脚本的另一种方法是简单地在运行时管理这些依赖关系。这可以通过使用提供包的库来实现。有很多方法可以实现这样的库,例如将它们直接安装到用户的环境中,或者操纵 sys.path
以便从本地缓存中使用它们。
这些方法与本 PEP 不冲突。像这样的 API:
env_mgr.install("rich")
env_mgr.install("click")
import rich
import click
...
当然可行。但是,这样的库可以在不需要任何新标准的情况下编写,据 PEP 作者所知,这还没有发生。这表明这种方法并不像最初看起来那么有吸引力。还有启动 env_mgr
库本身的启动问题。最后,这种方法实际上没有提供任何互操作性优势,因为它没有使用依赖项列表的标准形式,因此其他工具无法访问这些数据。
无论如何,这样的库仍然可以从本提案中获益,因为它可以包含一个 API 来从脚本依赖项块中读取要安装的包。这将提供相同的功能,同时允许与支持此规范的其他工具互操作。
# Script Dependencies:
# rich
# click
env_mgr.install_dependencies(__file__)
import rich
import click
...
为什么不使用带有 pyproject.toml
的 Python 项目?
再次强调,这里一个关键问题是,本提案的目标受众是编写不打算分发的脚本的人。有时脚本会被“共享”,但这比“分发”要非正式得多——通常涉及通过电子邮件发送脚本,并附带一些关于如何运行它的书面说明,或者将脚本的链接发送给某人。
期望这样的用户学习 Python 包的复杂性是一个巨大的复杂性提升,几乎肯定会让人觉得“Python 对脚本来说太难了”。
此外,如果这里期望的是 pyproject.toml
将以某种方式被设计用于在适当位置运行脚本,那么这是标准中目前不存在的新功能。至少,在 Discourse 上关于使用 pyproject.toml
用于不会作为轮子分发的项目的当前讨论 解决之前,这不是一个合理的建议。即使在那之后,它也无法解决“将脚本通过 gist 或电子邮件发送给某人”的使用场景。
为什么不使用 requirements 文件来管理依赖项?
将你的要求放在一个 requirements 文件中,并不需要一个 PEP。你现在就可以做到,事实上,许多临时解决方案很可能都是这样做的。但是,如果没有标准,就无法知道如何找到脚本的依赖项数据。此外,requirements 文件格式是特定于 pip 的,因此依赖它的工具依赖于 pip 实现的细节。
因此,为了制定一个标准,需要做两件事。
- 一个标准化的 requirements 文件格式的替代方案。
- 一个标准,用于如何为给定的脚本定位 requirements 文件。
第一项是一项重大的任务。它已经多次被讨论,但迄今为止还没有人尝试实际去做。最可能的方法是为目前使用 requirements 文件解决的各个用例开发标准。这里一个选择是让本 PEP 仅仅定义一个新的文件格式,它只是一个包含 PEP 508 要求的文本文件,每行一个。这样就只剩下如何定位该文件的问题了。
这里“显而易见”的解决方案是做一些类似于将文件命名为与脚本相同,但扩展名为 .reqs
(或类似的东西)。但是,这仍然需要两个文件,而现在只需要一个文件,因此与“更好的批处理文件”模型不匹配(shell 脚本和批处理文件通常是自包含的)。它要求开发者记住将这两个文件放在一起,而且这并不总是可能的。例如,系统管理策略可能要求某个目录中的所有文件都是可执行的(例如,Linux 文件系统标准要求 /usr/bin
中的所有文件都是可执行的)。有些脚本共享方法(例如,在像 Github 的 gist 或公司内部网这样的文本文件共享服务上发布脚本)可能不允许从脚本的位置推导出关联的 requirements 文件的位置(像 pipx
这样的工具支持直接从 URL 运行脚本,因此“下载和解压缩包含脚本及其依赖项的 zip 文件”可能不是一个合适的条件)。
从本质上来说,这里的问题是,有一个明确的要求,即格式支持将依赖项数据存储在脚本文件本身中。不这样做的方法只是忽略了这个要求。
脚本是否能够指定包索引?
依赖项元数据是关于代码依赖于哪些包,而不是关于这些包来自哪里。脚本的元数据和分发包的元数据(如 pyproject.toml
中定义的)之间没有区别。在这两种情况下,依赖项都以“抽象”形式给出,没有指定如何获取它们。
当然,有些使用依赖项信息的工具可能需要定位具体的依赖项工件——例如,如果它们期望创建一个包含这些依赖项的环境。但是,它们选择执行此操作的方式将与工具的 UI 紧密相关,本 PEP 不尝试规定工具的 UI。
在 前面提到的 pip-run 问题 中,对这一点,尤其是对 pip-run
工具所做的 UI 选择进行了更多讨论。
本地依赖项怎么办?
这些可以在不需要特殊元数据和工具的情况下处理,只需将依赖项的位置添加到 sys.path
中即可。本 PEP 对这种情况根本没有必要。另一方面,如果“本地依赖项”是实际在本地发布的分布,那么它们可以使用 PEP 508 要求以通常的方式指定,并在运行工具时使用工具的 UI 指定本地包索引。
未解决的问题
目前还没有。
版权
本文件放置在公共领域或 CC0-1.0-Universal 许可下,以更宽松的许可为准。
来源:https://github.com/python/peps/blob/main/peps/pep-0722.rst
最后修改时间:2023-10-21 10:30:17 GMT