Python 打包——过去、如今与将来

英文 | Python packaging - Past, Present, Future【1】html

原做 | BERNAT GABOR前端

译者 | 豌豆花下猫python

声明 :本文得到原做者受权翻译,转载请保留原文出处,请勿用于商业或非法用途。git

你是否想过在运行 pip install 时究竟发生了什么?这篇文章将给你一个关于过去所涉及的步骤的详细综述,以及它是如何随着 PEP-517 和 PEP-518 的采用而改变的。github

前一篇文章中,我描述了如何作到安装三种类型的内容:源码树(source tree)、源发行版(source distribution)和 wheel。只有最后两种类型会被上传到 PyPi 中央存储仓,但你也能够得到源码树(例如,经过为 pip 加入 git 协议)。与其它类型相比,wheel 的优势是不须要在用户机器上进行任何构建操做;只须要下载和提取。segmentfault

构建 Python 包

如今能够独立出构建的环境(用户或开发者的机器),但你仍然须要构建包(sdist 或 wheel)。为了作到这一点,你须要一些适当的构建器。在过去,对第三方包的需求很早就表现出来了。后端

遵循内置电池的原则,在 2000 年的 Python 1.6 中,distutils【2】包被添加进 Python 标准库中。它引入了包含构建逻辑的setup.py 文件的概念,并经过python setup.py 命令触发。api

它容许用户将代码打包成库,但没有声明(declaration)及自动安装依赖库等功能。并且,它的升级周期直接与核心解释器的发布周期绑定。安全

setuptools 于 2004 年建立,它构建在 distutils 之上,并扩展了其它优秀的特性。它很快变得很是流行,以致于大多数 Python 安装包开始将其与核心解释器一块儿提供。函数

在那个时候,全部的包都是源发行版。wheel 分发方式出现得很晚,是在 2014 年。distutils 是在只有少数很是精通打包的人的时候建立的。所以它是很是灵活和命令式的(imperative),你写一个 Python 脚本,能够修改包生成过程当中的每一步。

但这样作的缺点是,它一点也不容易学习和理解。随着 Python 的流行,这开始成为一个愈来愈严重的问题,由于有愈来愈多的用户对 Python 内部的工做原理不是很精通。

Charles PH 摄/Unsplash--ehhh

构建依赖项

关于安装一个源发行版,pip 主要作了如下工做:

  1. 找到这个包
  2. 下载源发行版并提取它
  3. 在提取的文件夹上运行python setup.py install(进行构建+安装)

开发者运行python setup.py sdist 生成分发包,运行python setup.py upload 上传到中央存储仓(上传命令在 2013 年被弃用了,由于有 twine【3】工具,更主要是由于 upload 使用了不安全的 HTTP 链接,并且上传命令会作一次新的构建,也就不容许最终用户在实际上传以前检测(inspect)生成的包)。

当 pip 运行python setup.py install时,它使用 Python 解释器来安装包。所以,构建操做能够访问该解释器中已经存在的全部三方包。最值得注意的是,它彻底使用了安装在主机 Python 解释器上的 setuptools 版本。若是一个包使用了 setuptools 的新版本特性,那么完成安装的惟一方法就是首先更新已安装的 setuptools。

若是新版本包含了能破坏其它包的 bug,就会致使出问题。在用户没法更改已安装包的系统上,这尤为麻烦。当构建器(例如 setuptools)但愿使用其它辅助包(例如 cython)时,这也是个问题。

若是缺乏构建器的辅助,一般会抛出导包失败的错误:

File "setup_build.py", line 99, in run
    from Cython.Build import cythonize
ImportError: No module named Cython.Build

在开发者们这边,没办法提供此类构建依赖项。而对于用户这边,则须要预先安装全部的包构建依赖,即便他们不会在运行时使用到。为了解决这个问题, PEP-518【4】被建立了。

其思想是,与其将主机的 Python 与其当前安装的构建包一块儿使用,不如给软件包提供一种能力,令其清楚地说明其构建操做所需的内容。另外,与其在主机 Python 上提供此功能,咱们是建立了一个独立的 Python(相似某种虚拟环境)来运行打包。

python setup.py install 如今能够:

  1. 建立一个临时文件夹
  2. 建立一个隔离的(从三方库的 site packages 中)Python 环境 python -m virtualenv our_build_env,让咱们将这个 Python 可执行文件称为python_isolated
  3. 安装构建的依赖项
  4. 经过python_isolated setup.py bdist_wheel,生成一个用于安装的 wheel
  5. 提取 wheel 到 Python 的 site packages 文件夹

有了这个,咱们能够安装依赖于cython 的包,但没必要在运行的 Python 环境中实际安装cython。指定构建依赖项的文件与方法的是pyproject.toml元数据文件:

[build-system]
requires = [
    "setuptools >= 40.8.0",
    "wheel >= 0.30.0",
    "cython >= 0.29.4",
]

此外,它还容许打包者指定他们须要的最小版本,而借助用户机器上的 pip,能够轻易地找出这些版本。

当在开发者的机器上生成源发行版或 wheel 时,也可使用相同的机制。当一我的调用pip wheel . --no-deps命令时,该命令会自动在后台建立一个包含构建依赖项的独立 Python,而后在该环境中调用python setup.py bdist_wheelpython setup.py sdist 命令。

Bruce Galpin摄/Unsplash--yay!

多样的打包工具

但这里还有一个问题。请注意,全部这些操做仍然须经过 20 年前引入的机制,即执行setup.py。整个生态系统仍然构建在 distutils 和 setuptools 的接口基础之上,因为试图保持向后兼容性,无法做太大的变动。

此外,在打包过程当中执行用户端 Python 代码是危险的,这可能会致使经验较少的用户难以调试的细微错误。命令式的(imperative)构建系统在 20 年前对于灵活性来讲很是重要,当时咱们还不知道全部的状况,可是如今咱们已经认识清楚了,极可能能够为不一样的状况建立出很是健壮和简单的包构建器。

引用 Paul Ganssle【5】(setuptools 与 dateutil 的维护者)的话:

理想状况下,默认选项应该是一个声明式的(declarative)构建配置,适用于 99% 的状况,再提供一个退回到命令式系统的选项,供真正须要灵活性时使用。在这状况下,若是你发现还须要选择用命令式的构建,那么咱们能够认为出现了坏味道代码。

setup.py 的最大的问题是大多数人是声明式地使用它,因此当他们用命令式时,每每会将 bug 引入到构建系统。一个这样的例子:若是你有一个 Python2.7 的依赖项,你可能会试图有条件地在 setup.py 中指定 sys.version,但 sys.version 仅指的是执行构建的解释器;相反,你应该对需求项使用声明式的环境标记…

在 2015 年的引入的flit【6】已经证实了这一假设的正确性。它已经成为许多 Python 新手最喜欢的打包工具,由于它能够确保新用户避免不少这样的麻烦。然而,要达到这个目的,flit 必须再次构建在 distutils/setuptools 之上,这使得它的实现很是关键,而且代码仓出现至关多的垫片层(例如,它仍然为源发行版生成 setup.py 文件)。

如今是时候把它从这些束缚中解放出来了,同时也鼓励其余人构建本身的打包工具来简化打包,是时候让 setup.py 成为例外而不是默认的了。setuptools 计划提供【7】一个用户专用的setup.cfg 接口来起带头做用,当一个 PEP-517 系统就位时,在大多数状况下,你应该选择它而不是使用 setup.py。

为了避免把全部东西都绑定到 setuptools 和 distutils 上,并使后端的构建变得便利, PEP-517【8】被建立了。它将构建器分红后端和前端。前端提供了一个隔离的 Python 环境,知足全部声明的构建依赖项;后端提供了钩子,被前端从其隔离环境中调用,以生成源发行版或者 wheel。

此外,咱们再也不经过 setup.py 文件或命令与后端通讯,而是使用了 Python 模块和函数。全部后端的打包必须提供一个 Python 对象 API,至少实现 build_wheel【9】和 build_sdist【10】两个方法。该 API 对象是经过 pyproject.toml 文件指定的,使用build-backend 键值:

[build-system]
requires = ["flit"]
build-backend = "flit.api:main"

上述代码对于前端意味着,你能够经过在隔离的 Python 环境中运行它来控制后端:

import flit.api
backend = flit.api.main

# build wheel via 
backend.build_wheel()

# build source distribution via
backend.build_sdist()

由后端决定要在哪里和怎样公开本身的官方 API:

  1. flit【11】经过flit.buildapi实现
  2. setuptools【12】提供了两种变体:setuptools.build_meta(后面会解释缘由)
  3. poetry【13】经过poetry.masonry.api实现

由于这些,咱们就拥有了再也不受 distutils 遗留决策约束的打包工具。

Sarthak Dubey摄/Unsplash--更多 yay!

tox 和打包

tox 是一个测试工具【14】,大多数项目使用它来确保某个包在多个 Python 解释器上的版本兼容性。它还能够轻松地建立 Python 环境,在里面安装被监测的包,从而更快地复现问题。

为了可以测试一个包,它首先须要构建一个源发行版。虽然 PEP-518 和 PEP-517 都带有好的意图,可是在某些状况下,启用它们可能会破坏打包过程。所以,当 tox 在 3.3.0 版本中添加隔离构建时,决定暂时不默认启用它。你须要手动启用它(可能会在今年晚些时候——2019 年的版本 4 中默认启用)。

一旦你指定了一个pyproject.toml ,写了适当的requiresbuild-backend,你须要启用tox.ini 中的isolated_build标志:

[tox]
isolated_build = True

在此以后,在打包过程当中【15】,tox 将在独立的 Python 环境中为每一个 PEP-518 提供构建依赖项,来构建源发行版,并调用 PEP-517 所述的构建后端。

若不启用该功能,tox 将使用老方法构建源发行版,也就是使用安装了 tox 的解释器来调用python setup.py sdist命令。

Matthew Henry摄/Unsplash--这里没有免费的午饭呢!

小结

Python 打包官方但愿全部这些都是有意义的,并所以拥有一个更用户友好的、防错的(error proof )和健壮的构建。这些标准的规范是在 2015 年至 2017 年的长期主题中写做并争论出来的。这两个提案(PEP-517/518)被认为是足够好的,能够得到最大的收益,可是一些不太主流的场景可能会被忽略。

若是你的状况是被忽略的,不要担忧,若是咱们认为必要的话,PEP 在任什么时候候都是拥抱改进意见的。在本系列的下一篇文章中【16】,我将讨论社区在发布这两个 PEP 时碰撞到的一些痛点。这些都是咱们应该吸收的教训,而且代表着咱们仍有一些工做要作。还不是一切都完美,但咱们正在变得更好。若是你能够帮帮忙,就加入打包社区吧,让咱们一块儿把事情作得更好!

附1:勘误

前一篇文章中,source distribution 被译成“源码分发”,但它还有一个更被人采用的译法是“源发行版”,为了便于接受,因此本文已做修改。翻译匆忙,若有错误,欢迎读者指正!万分感谢!PS:后续如有修正,会在知乎专栏修改,请关注“Python进阶之旅”:https://zhuanlan.zhihu.com/pythonCat

附2:相关连接

[1] Python packaging - Past, Present, Future: https://www.bernat.tech/pep-5...

[2] distutils: https://packaging.python.org/...

[3] twine: https://pypi.org/project/twine/

[4] PEP-518 : https://www.python.org/dev/pe...

[5] Paul Ganssle: https://twitter.com/pganssle

[6] flit: https://pypi.org/project/flit/

[7] 计划提供: https://github.com/pypa/setup...

[8] PEP-517: https://www.python.org/dev/pe...

[9] build_wheel: https://www.python.org/dev/pe...

[10] build_sdist: https://www.python.org/dev/pe...

[11] flit: https://flit.readthedocs.io/e...

[12] setuptools: https://setuptools.readthedoc...

[13] poetry: https://poetry.eustace.io/doc...

[14] 测试工具: https://tox.readthedocs.io/en...

[15] 打包过程当中: https://tox.readthedocs.io/en...

[16] 下一篇文章中: https://www.bernat.tech/growi...

公众号【Python猫】, 本号连载优质的系列文章,有喵星哲学猫系列、Python进阶系列、好书推荐系列、技术写做、优质英文推荐与翻译等等,欢迎关注哦。