Python 打包——过去、现在与未来

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

原作 | BERNAT GABOR

译者 | 豌豆花下猫

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

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

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

构建 Python 包

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

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

它允许用户将代码打包成库,但没有声明(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进阶系列、好书推荐系列、技术写作、优质英文推荐与翻译等等,欢迎关注哦。

Image placeholder
qingcai
未设置
  17人点赞

没有讨论,发表一下自己的看法吧

推荐文章
Python 教程-了解Python

什么是Python Python能干什么? 有什么特点? 什么是Python 官方介绍: Python是一个易于学习、功能强大的编程语言。它拥有高效高级的数据结构和一种简单有效的面向对象编程的

Python 教程-Python 安装

在Windows上安装 访问https://www.python.org/downloads/并下载最新版本。在撰写时当前最新是3.8。在安装的时候和其他软件一样,无脑式下一步。需要注意的是如果在W

Python入门教程_2. 使用 Python 解释器

2.1.调用解释器 Python解释器通常安装在目标机器上的/usr/local/bin/python3.7目录下;把/usr/local/bin目录放进你的Unixshell的搜索路径里,确保它可以

Python入门教程_3. Python 简介

在下面的例子中,输入和输出分别由大于号和句号提示符(>>>和...)标注:如果想重现这些例子,就要在解释器的提示符后,输入(提示符后面的)那些不包含提示符的代码行。需要注意的是在练习中遇到的从属提示符

Python入门教程_4. 深入 Python 流程控制

除了刚刚介绍的while语句,Python还有一些在其他语言中常见的控制流语句,并做了一些改动。 4.1.if语句 也许最著名的语句是if语句了。 例如: >>>x=int(input("Please

【python测试开发栈】帮你总结python random模块高频使用方法

随机数据在平时写python脚本时会经常被用到,比如随机生成0和1来控制逻辑、或者从列表中随机选择一个元素(其实抽奖程序也类似,就是从公司所有人中随机选择中奖用户)等等。这篇文章,就帮大家整理在pyt

流畅的Python读书笔记 --- 第一章 Python数据模型

近期开始读“流畅的Python”这本书,想把自己的读书笔记分享给大家,希望能帮到也对这本书感兴趣但是没时间看的各位。(文章中大部分的话和图片摘录总结自“流畅的Python”一书,以及python官方网

【python测试开发栈】帮你总结python time模块高频使用方法

在平时写python脚本时,时间是我们经常用到的数据,比如:时间戳、前端展示的对应格式的时间等,在python中主要有三个和时间处理相关的模块:time、datetime、calendar,这篇文章主

Python 打包的现状:包的三种类型

英文|ThestateofPythonPackaging【1】原作|BERNATGABOR译者|豌豆花下猫声明:本文获得原作者授权翻译,转载请保留原文出处,请勿用于商业或非法用途。pip19.0已经于

Python可视化 | Seaborn5分钟入门(一)——kdeplot和distplot

微信公众号:「Python读财」如有问题或建议,请公众号留言Seaborn是基于matplotlib的Python可视化库。它提供了一个高级界面来绘制有吸引力的统计图形。Seaborn其实是在matp

Python可视化 | Seaborn5分钟入门(二)——barplot&countplot&pointplot

微信公众号:「Python读财」如有问题或建议,请公众号留言Seaborn是基于matplotlib的Python可视化库。它提供了一个高级界面来绘制有吸引力的统计图形。Seaborn其实是在matp

Python可视化 | Seaborn5分钟入门(七)——pairplot

微信公众号:「Python读财」如有问题或建议,请公众号留言Seaborn是基于matplotlib的Python可视化库。它提供了一个高级界面来绘制有吸引力的统计图形。Seaborn其实是在matp

Python可视化 | Seaborn5分钟入门(三)——boxplot和violinplot

微信公众号:「Python读财」如有问题或建议,请公众号留言Seaborn是基于matplotlib的Python可视化库。它提供了一个高级界面来绘制有吸引力的统计图形。Seaborn其实是在matp

Python可视化 | Seaborn5分钟入门(四)——stripplot和swarmplot

微信公众号:「Python读财」如有问题或建议,请公众号留言Seaborn是基于matplotlib的Python可视化库。它提供了一个高级界面来绘制有吸引力的统计图形。Seaborn其实是在matp

Python可视化 | Seaborn5分钟入门(六)——heatmap热力图

微信公众号:「Python读财」如有问题或建议,请公众号留言Seaborn是基于matplotlib的Python可视化库。它提供了一个高级界面来绘制有吸引力的统计图形。Seaborn其实是在matp

Python可视化 | Seaborn5分钟入门(五)——lmplot

微信公众号:「Python读财」如有问题或建议,请公众号留言Seaborn是基于matplotlib的Python可视化库。它提供了一个高级界面来绘制有吸引力的统计图形。Seaborn其实是在matp

超8千Star,火遍Github的Python反直觉案例集!

大数据文摘授权转载作者:SatwikKansal译者:暮晨Python,是一个设计优美的解释型高级语言,它提供了很多能让程序员感到舒适的功能特性。但有的时候,Python的一些输出结果对于初学者来说似

面向回家编程!GitHub标星两万的”Python抢票教程”,我们先帮你跑了一遍

盼望着,盼望着,春节的脚步近了,然而,每年到这个时候,最难的,莫过于一张回家的火车票。据悉,今年春运期间,全国铁路发送旅客人次同比将增长8.0%。达到4.4亿人次,2020年铁路春运自1月10日开始,

可视化编程是否真的没有未来?程序员:它有“七宗罪”

今天想聊聊可视化编程(visual-programming)的未来发展,喂喂,咱们这儿还没开始,各位大佬先别急着走啊您……确实,可视化这个概念跟任何技术并称,都是技术前沿、下一个风口、万亿市场的代名词

未来有发展前景的IT技术岗位盘点

众所周知,在互联网时代,IT技术岗位是互联网公司和企业的核心发展力量。现在我们来盘点一下未来有发展前景的IT技术岗位。一般来说,IT技术岗位可以分为开发岗位、测试岗位、UI设计等,下面我将从这几个岗位

深信服何朝曦:畅想数字化未来 安全是重要保障

处在数字化转型的风口浪尖上,企业都在大谈数字化转型,都希望能够通过数字化转型为企业带来更多的红利。那究竟什么才是数字化转型?企业的数字化未来又是怎样的?针对这些问题,深信服给出了他的答案。8月15日-

云数据管理会成为DataOps的未来吗?

如今,现代化的数据管理平台正在成为企业的首先,而传统的数据运维应用平台则逐渐被边缘化。尤其在AI和机器学习技术的推动下,企业数据正在走向以云为核心的数字化征程。根据思科的一份数据显示:到2021年,企

盘点 | 物联网未来的9大主要安全挑战

物联网(IoT)是数字转型时代最热门的技术之一,其能够将一切都连接到互联网。它是智能家居、自动驾驶汽车、智能电表和智能城市背后的核心技术。但是物联网(IoT)的未来将面临九个主要的安全挑战。在过去的几

混合云或成未来主流趋势

“混合云是指来自公有云的计算、存储与来自私有云基础架构的组合,并且私有云架构通常以本地云软件堆栈的形式运行在服务器上。在混合云模式下,公有云和私有云环境既彼此独立,又能通过连接加密的形式相互关联。”混

过了35岁,90%以上DBA都在迷茫:未来要何去何从?

墨墨导读:如今,越来越多35岁以上的数据库从业者面临着各种各样的压力,对前进的方向有些迷茫,本文就这一普遍现象做出了简要的分析,包括压力产生的原因、如何应对这些压力、如何迎接未来的挑战!你的事业做了十