
PyPA 发布 PEP 723 规范:Python 单文件脚本可内嵌元数据
来源标题:PEP 723 – Inline script metadata
PyPA 在规范页面维护 PEP 723 的正式版本,该规范定义了一种可嵌入单文件 Python 脚本的元数据格式,用于帮助启动器、IDE 等外部工具识别和处理脚本。这类脚本通常以单个文件形式通过邮件、链接或聊天窗口分享,不会发展为带 pyproject.toml 的完整项目,此前缺乏为执行工具定义元数据的标准机制。原始 PEP 文档已作为历史文档保留。
以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。
本 PEP 是一份历史文档。最新、权威的规范《Inline script metadata》维护在 PyPA specs 页面。关于如何提出变更,请参见 PyPA 规范更新流程。
本 PEP 规定了一种元数据格式,可嵌入到单文件 Python 脚本中,以帮助启动器、IDE 以及其他可能需要与这类脚本交互的外部工具。
Python 常被用作脚本语言,Python 脚本是 shell 脚本、批处理文件等(更好的)替代方案。当 Python 代码以脚本形式组织时,通常存储为单个文件,并且不期望存在任何其他可用于导入的本地代码。因此,可以通过任意基于文本的方式与他人分享,例如电子邮件、指向脚本的 URL,甚至聊天窗口。以这种方式组织的代码可能永远作为单个文件存在,永远不会成为一个拥有自己目录和 pyproject.toml 文件的完整项目。
用户在使用这种方式时遇到的一个问题是:对于那些负责执行此类脚本的工具来说,没有标准机制来定义元数据。例如,运行脚本的工具可能需要知道需要哪些依赖项,或者支持哪些 Python 版本。
目前还没有解决这一问题的标准工具,本 PEP 也不试图定义一个。然而,任何解决这一问题的工具都需要知道脚本的运行时需求。通过定义一种存储此类元数据的标准格式,现有工具以及未来任何工具都将能够获取该信息,而无需用户在其脚本中包含特定于工具的元数据。
本 PEP 定义了一种将元数据嵌入脚本本身而非外部文件的机制。
该元数据格式在设计上类似于 Python 项目目录中 pyproject.toml 文件的数据布局,以便为有编写 Python 项目经验的用户提供熟悉的体验。通过使用类似的格式,我们避免了打包工具之间不必要的不一致,这是用户在最近的打包调查中表达的一个常见困扰。
以下是本 PEP 希望支持的一些用例:
本 PEP 定义了一种元数据注释块格式,其灵感松散地来源于 [2] reStructuredText 指令。
任何 Python 脚本都可以有顶级注释块,这些注释块必须以行 # /// TYPE 开头,其中 TYPE 决定如何处理内容。也就是说:一个 #,后跟一个空格,后跟三个正斜杠,后跟一个空格,后跟元数据类型。块必须以行 # /// 结束。也就是说:一个 #,后跟一个空格,后跟三个正斜杠。TYPE 只能由 ASCII 字母、数字和连字符组成。
这两行(# /// TYPE 和 # ///)之间的每一行都必须是始于 # 的注释。如果 # 后面有字符,则第一个字符必须是空格。嵌入内容是通过去掉每一行的前两个字符(如果第二个字符是空格),否则只去掉第一个字符(这意味着该行仅由一个 # 组成)来形成的。
结束行 # /// 的优先级在下一行不是如上所述的有效嵌入内容行时生效。例如,以下是一个单一且完全有效的块:
# /// some-toml
# embedded-csharp = """
# /// <summary>
# /// text
# ///
# /// </summary>
# public class MyClass { }
# """
# ///
起始行不得放在另一个起始行与其结束行之间。在这种情况下,工具可以产生错误。未闭合的块必须被忽略。
当定义了多个相同 TYPE 的注释块时,工具必须产生错误。
读取嵌入元数据的工具可以遵循标准的 Python 编码声明。如果它们选择不这样做,则必须将文件按 UTF-8 处理。
以下是可用于解析元数据的规范正则表达式:
(?m)^# /// (?P<type>[a-zA-Z0-9-]+)$\s(?P<content>(^#(| .*)$\s)+)^# ///$
在文本规范与正则表达式之间存在差异的情况下,以文本规范为准。
工具不得读取类型未经本 PEP 或未来 PEP 标准化的元数据块。
第一种元数据块名为 script,包含脚本元数据(依赖数据和工具配置)。
本文档可以包含顶级字段 dependencies 和 requires-python,并且可以选择性地包含 [tool] 表。
[tool] 表可供任何工具、脚本运行器或其他工具用来配置行为。其语义与 pyproject.toml 中的 tool 表相同。
如果无法提供指定的依赖项,脚本运行器必须报错。
如果无法提供满足指定 requires-python 的任何 Python 版本,脚本运行器应报错。
以下是一个包含嵌入元数据的脚本示例:
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "requests<3",
# "rich",
# ]
# ///
import requests
from rich.pretty import pprint
resp = requests.get("https://peps.python.org/api/peps.json")
data = resp.json()
pprint([(k, v["title"]) for k, v in data.items()][:10])
参考实现
以下是在 Python 3.11 或更高版本上读取元数据的示例。
import re
import tomllib
REGEX = r'(?m)^# /// (?P<type>[a-zA-Z0-9-]+)$\s(?P<content>(^#(| .*)$\s)+)^# ///$'
def read(script: str) -> dict | None:
name = 'script'
matches = list(
filter(lambda m: m.group('type') == name, re.finditer(REGEX, script))
)
if len(matches) > 1:
raise ValueError(f'Multiple {name} blocks found')
elif len(matches) == 1:
content = ''.join(
line[2:] if line.startswith('# ') else line[1:]
for line in matches[0].group('content').splitlines(keepends=True)
)
return tomllib.loads(content)
else:
return None
通常工具会像包管理器或依赖更新那样编辑依赖项。
(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。