【译文】Python 打包,一年之后:回顾 2023 年的 Python 打包

一年前,我写过一篇关于 Python 打包现状的文章。该领域的工具数量众多,强调编写模糊的标准而非围绕唯一真正的工具,基于 venv- 的生态系统错综复杂,而非类似于 node_modules 的解决方案。过去一年有什么变化?是有所改善,还是一切照旧,抑或是情况比以前更糟了?

工具

原帖列出了一堆打包工具,称 14 种工具至少有 12 种太多了。我的想法是,大多数人都会乐于使用一种工具来完成所有工作,但科学界的 Python 人士可能会有一些特殊要求,最好是使用第二种工具。

在去年文章中提到的工具中,所有工具似乎都还在维护。除了 Flit(过去 30 天内没有新提交)和 virtualenv(只有自动和半自动版本升级),其他工具都有最近的提交、拉取请求和问题。

所有这些工具仍在使用中。Françoise Conil 分析了所有 PyPI 软件包,并检查了它们的 PEP 517 构建后端:setuptools 最受欢迎(50k 个软件包),Poetry 以 41k 位居第二,Hatchling 以 8.1k 位居第三。其他用户数超过 500 的工具包括 Flit(4.4k)、PDM(1.3k)和 Maturin(1.3k,基于 Rust 的软件包的构建后端)。

当然,还有一些新工具。我最感兴趣的是 PosyRye。Posy 是 Nathaniel J. Smith(三重奏的创始人)的项目,Rye 则是 Armin Ronacher(Flask 的创始人)的项目。这两个项目的愿景都是管理 Python 解释器和项目,但不使用定制的构建后端(而是使用类似 hatchling 的东西)。Posy 构建在 PyBI(一种用于发布 Python 解释器二进制文件的格式,由 Smith 在 PEP 711 草案中提出)之上,而 Rye 则使用 Gregory Szorc 预构建的 Pythons。Rye 看起来相当完整和可用,而 Posy 目前只是 PyBI 格式的 PoC,只提供了一个带有预装软件包的 REPL。

Posy 和 Rye 都是用 Rust 编写的。一方面,管理 Python 解释器的部分不是用 Python 写的是合情合理的,因为这样就需要一个独立的 Python(不是由 Posy/Rye 管理)来运行这些工具。但 Rye 也有自己的 Pyproject.toml Rust 解析器,而且它的许多命令也主要或大部分使用 Rust 实现(有时也调用一次性的 Python 脚本;不过创建 venvs、安装软件包和处理锁文件的主要任务分别交给了 venvpip pip-tools)。

说到 Rust 和 Python,在过去的一年中,还有另一个与之类似的项目得到了长足的发展(并获得了大量资金)。这个项目就是 Ruff,它是一个字符串和代码格式化工具。Ruff 格式化 Python 代码,用 Rust 编写。这意味着它比现有的 Python 工具快 10-100 倍(根据 Ruff 自己的基准测试)。我想,快是好事,但这对 Python 有什么影响呢?事实上,软件包工具(除了快速的依赖性求解器之外,可能并不是什么火箭科学,而且通常需要访问 Python 内部结构才能完成工作)和代码格式化工具(需要深入理解 Python 语法,并将 Python 源解析为 AST,而这在 ast Python 模块中很容易实现)是用另一种语言编写的吗?这种趋势是否会让 Python 成为一种玩具语言(因为它通常也被认为是 NumPy 和朋友们的粘合剂语言)?另外,为什么对许多 Python 开发者来说很重要的工具也需要学习 Rust 呢?

标准

上次我们讨论打包标准时,重点关注了 PEP 582。它建议引入 __pypackages__,作为第三方软件包的安装位置,以每个项目为单位,不涉及虚拟环境,类似于 node_modules 对于 node 的作用。PEP 最终于 2023 年 3 月被否决。PEP 并不完美,其中一些选择值得商榷或不够充分(例如不在父目录中递归搜索 __pypackages__,或只关注简单的用例)。时至今日,还没有人提出类似的新标准(更好的设计)。

另一个有争议的话题是锁文件。打包系统的锁文件对于可重现的依赖安装非常有用。锁文件记录了所有已安装的软件包(即包括传递依赖)及其版本。锁文件通常包括已安装软件包的校验和(如 sha512),而且通常支持区分通过不同依赖组(运行时、构建时、可选、开发等)安装的软件包。

实现这一目标的经典方法是 requirements.txt 文件。这些文件是 pip 特有的,只包含软件包列表、版本和可能的校验和。这些文件可以由 pip freeze pip-tools 中的第三方 pip-compile 生成。pip freeze 非常基本,而 pip-compile 除了生成多个 requirements.in 文件、编译它们并希望没有冲突外,无法处理不同的依赖组。

Pipenv、Poetry 和 PDM 有各自的 lockfile 实现,彼此不兼容。Rye 是 pip-tools 的附属品。Hatch 核心中没有任何内容;他们正在等待标准实现(不过也有一些插件)。PEP 665 于 2022 年 1 月被否决。它的作者 Brett Cannon 正在开发一个可能成为标准的 PoC(名为 mousebender)。

这就是 Python 打包界所采用的工作模式的危险所在。即使是锁文件这么简单的事情,也至少有四种不兼容的标准。尽管至少有四种实现实现了大致相同的目标,而且其他生态系统也曾经历过这种情况,但由于 “反应冷淡”,一个规范的尝试还是被否决了。

另一个对 Python 很重要的东西是扩展模块。扩展模块是用 C 语言编写的,通常用于与其他语言编写的库进行交互(有时也用于提高性能)。Poetry、PDM 和 Hatchling 其实并不支持构建扩展模块。Setuptools 支持;SciPy 和 NumPy 则从自定义的 numpy.distutils 移植到了 Meson。Python 的 PyO3 Rust 绑定背后的团队开发了 Maturin,它允许构建基于 Rust 的扩展模块–但如果你使用的是 C 语言,它就没什么用了。

2023 年被接受的打包相关标准并不多。值得一提的标准是 PEP 668,它允许发行版通过添加一个外部管理文件(EXTERNALLY-MANAGED file)来阻止 pip 运行(以避免破坏发行版拥有的网站软件包)。该标准于 2022 年 6 月被接受,但 pip 在 2023 年 1 月才实现了对它的支持,而许多发行版已经在 2023 年启用了这一功能。防止系统崩溃是件好事。

但有些标准还是通过了。除了一些次要的小标准外,最突出的 2023 标准应该是 PEP 723:内联脚本元数据。它允许在文件顶部添加注释块,以工具可以使用的方式说明依赖关系和最小 Python 版本。它超级有用吗?我不这么认为;用 pyproject.toml 建立一个项目很容易让事情发展壮大。如果要通过 GitHub gist 发送内容,只需创建一个 repo 即可。如果通过电子邮件发送,只需压缩文件夹即可。这种方法会导致编程混乱,没有源代码控制。

学习曲线和 “简单 “的欺骗性

Microsoft Word 很简单,是初学者的绝佳写作工具。只需单击一下,就能将文字变粗。点击两下还能让文字变蓝。但它很容易造成前后不一致的混乱。在制作章节标题时,许多用户可能只是将文字变粗变大一点,而没有任何一致性或语义[1]。在 Word 中制作一个具有语义格式的一致文档很难。添加章节编号需要选择标题并将其转化为列表。据说这其中还涉及到一些魔法,但这些魔法对我不起作用,我必须告诉 Word 更新标题样式。即使你试着把事情做得很好,Word 也会随机中断、弄乱样式、混淆样式和内联临时格式,你的文档在不同的电脑上看起来可能会不一样。

LaTeX 对初学者来说非常容易混淆,学习曲线非常长。当然,你也可以到处写 \textbf{hello}。但只要稍加学习,你就能制作出精美的文档。你将定义一个 \code{} 命令,它今天可以使代码变成monospace字体并添加边框,但如果你愿意,它明天可能会改变背景并排版成Comic Sans字体。你将使用可以从外部文件渲染代码并带有语法高亮的软件包。默认情况下,标题编号是打开的,但也可以轻松禁用某一节的标题编号。例如,LaTeX 还能自动将新章节放在新页面上。LaTeX 是为科学出版而设计的,因此它对数学和参考书目等都有出色的支持。

现在我们来谈谈编程。Python 很简单,是初学者的最佳编程语言。一行代码就能写出 hello world。语法更简单,没有令人困惑的 C 语言遗留代码(如基于索引的 for 循环)或机器级代码(如 switch 中的 break),也没有指针。你也根本不需要编写类;你不需要编写一个类,只是为了在类中放置一个 public static void main(String[] args) 方法[2]。你不需要集成开发环境,只需使用任何编辑器编写代码(即使是记事本.exe 也可以),然后将其保存为 .py 文件,并使用 python whatever.py 运行即可。

代码变得复杂了?不用担心,你可以将它拆分成多个 .py 文件,使用 import_of_other_file_without_py 就可以了。你是否需要更多的结构,也许是文件夹分组?那就忘了 python whatever.py吧,你必须使用 python -m whatever,你必须 cd 到你的代码所在的位置,或者使用PYTHONPATH,或者使用 pip 安装你的东西。这种简单而常见的操作(将东西归类到文件夹中)大大增加了复杂性。

标准库不够用[3],还需要第三方依赖?你会发现一些教程告诉你使用 pip 安装,但 pip 现在会告诉你使用 apt。而 apt 可能会起作用,但它可能会给你一个古老的版本,与你正在阅读的教程不符。或者它可能没有这个软件包。或者互联网会告诉你不要使用 apt 中的 Python软件包。所以现在你需要学习 venvs(这会增加复杂性,需要记住更多东西;大多数教程都教激活,venvs 很容易通过重命名文件夹等基本操作搞砸,而且你可能最终在 git 中使用 venv,或者在 venv 中使用你的代码)。或者,你需要从众多一站式管理工具中挑选一款。

在其他生态系统中,集成开发环境通常是必需品,即使对初学者来说也是如此。集成开发环境会强制您使用一个项目系统(也许默认情况下不是最好或最常用的系统,但它仍然是一个连贯的项目系统)。Java 会强制您按照 “1 个公有类 = 1 个文件 “的规则创建多个文件,而且这样做很容易,甚至不需要import

您想要文件夹吗?在 Java 或 C# 中,您只需在集成开发环境中创建一个文件夹,然后在其中创建一个类。新文件可能有不同的package/namespace,但集成开发环境会帮助您将正确的import/using添加到代码库中,而且不会出现使用过多目录(包括类似 src 这样的目录)或使用过少目录(没有为所有代码创建一个顶级包)而需要纠正所有导入的风险。在 Java 或 C# 中添加文件夹造成的干扰微乎其微。

项目系统还可以处理第三方软件包,而无需考虑从哪里下载、什么是虚拟环境以及如何在不同环境下激活它。只需点击几下即可完成。如果你不喜欢集成开发环境呢?在许多生态系统中,CLI 当然是可行的,它们有合理的 CLI 工具来完成常见的管理任务,以及构建和运行项目。

PEP 723 解决了一个非常小众的问题:单文件程序的依赖关系管理。对于打包社区来说,改善一次性事物和杂乱代码的生存环境显然比其他针对大项目的改进更为重要。

顺便说一句,你可以把这一课应用到静态和动态类型中。动态类型更容易上手,所需的键入量也更少,但编译类型检查可以防止许多 bug,而动态类型需要更高的测试覆盖率才能捕捉到这些 bug。这就是为什么 JS 世界有 TypeScript,这就是为什么 mypy/pyright/typing 在 Python 世界获得了大量的心智份额。

未来…

Python 打包讨论区,我们讨论了一些改进的方法。

例如,Gregory Szorc 发起了关于移植 setup.py 的讨论,他提出了一长串抱怨,指出了与打包界沟通的问题,以及文档混乱的问题(他的帖子值得一读,或者至少略读一下,因为它很长,而且充满了打包失败的例子)。有一页推荐使用 setuptools,另一页有四个选项,默认使用 Hatchling,还有一页仍在推广 Pipenv。我们一年前就看到过这种情况,这方面没有任何变化。一些人试图找到解决方案,一些人分享了他们的意见……然后 Discourse 版主决定保护他的 PyPA 朋友们,不让他们阅读用户反馈,于是锁定了主题。

还有许多其他关于愿景的主题,比如关于 10 年展望单一打包工具的主题。基于用户调查的战略讨论有了第二部分第一部分于 2023 年 1 月结束),但帖子数量少于第一部分,讨论也没有继续(还讨论了如何举行讨论)。有计划建立一个打包委员会–最好是按委员会进行设计。

……看起来非常暗淡

另一方面,如果您看看大多数打包工具 2023 年的贡献图,您可能会对打包生态系统的状况感到担忧。

  • Pip 拥有健康的贡献者组合和大量的提交。
  • Pipenvsetuptools 有两个主要提交者,但提交量仍然很健康。
  • Hatch 则是一个人的表演:Ofek Lev(项目创始人)提交了 184 次,第二名是 Dependabot,提交了 6 次,第三名的贡献者(人类)提交了 5 次。Hatch 和 Hatchling 的总线系数为 1。

非 PyPA 工具的情况也好不了多少:

  • Poetry 有两位顶级贡献者,但至少有四位人类贡献者的提交次数达到了两位数。
  • PDM 和 Hatch 一样,是一个人的表演。
  • Rye 有一位主要贡献者,三位提交次数达到两位数的贡献者;需要注意的是,它是一个相当新的项目(始于 2023 年 4 月底),不像其他项目那么受欢迎。

结论

我知道 PyPA 是一个由志愿者组成的松散协会。有时有人说 Python 打包管理局这个名字最初是个玩笑。不过,他们也是维护所有打包标准的组织,所以在打包方面他们是权威。例如,PEP 668 一开始就有一个警告块,说这是一个历史文档,而最新版本的规范就在 PyPA 的网站上(还有其他一些打包规范)。

PyPA 应该关闭或合并一些重复的项目,并与社区(包括非 PyPA 项目的维护者)合作,建立一个真正的打包工具。让事情变得更简单。避免编写 5 次做相同事情的代码。确保成千上万的项目不再依赖于总线系数为 1 或 2 的工具。将打包从一个问题和不可逾越的障碍转变为一个普通开发人员无需考虑的简单工具。

这不是火箭科学。很多语言,无论大小,都有一个连贯的打包生态系统(请阅读去年的文章,了解一些简单的例子)。与其关注规范和管理,不如专注于开发一个全面、可用、用户友好的工具。

脚注

[1] 现代 Word 至少让这一点变得更容易,因为标题样式在功能区中占据了首要位置;而在 Word 2003 及更早版本中,它们被隐藏在一个完全不显眼的组合框后面,上面写着 “正常”。
[2] C# 10 取消了制作带有 Main 方法的类的要求,它可以选取一个带有顶级语句的文件,并将其作为入口点。
[3] Python 标准库备受赞誉。与 C 语言相比,它的规模很大,但与 Java 或 C# 相比并无特别之处。它还充满了低质量的库,如 http.server 或 urllib.request,但有些人坚持只使用标准库。标准库的稳定性和可靠性也较差(不断地贬值和删除,新特性需要升级整个 Python)。所有 “严肃 “的用例,如网络开发或 ML/人工智能/数据科学,都不可能只使用标准库。

本文文字及图片出自 Python Packaging, One Year Later: A Look Back at 2023 in Python Packaging

余下全文(1/3)
分享这篇文章:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注