Python 包装的概述

作为一种通用的编程语言,Python 被设计成可以在许多方面使用。你可以建立网站或工业机器人,或为你的朋友设计一个游戏,以及更多,都使用相同的核心技术。

Python 的灵活性是为什么每个 Python 项目的第一步都必须考虑项目的受众和项目运行的相应环境。在写代码之前考虑打包问题可能看起来很奇怪,但这个过程对于避免未来的头痛问题有很大的作用。

这个概述提供了一个通用的决策树,用于推理 Python 的大量封装选项。继续阅读,为你的下一个项目选择最好的技术。

关于部署的思考

软件包的存在是为了安装(或 部署),所以在你打包任何东西之前,你要对下面的部署问题有一些答案:

  • 谁是你的软件的用户?你的软件是否会被其他从事软件开发的开发人员、数据中心的操作人员或不太懂软件的群体安装?

  • 你的软件是要在服务器、桌面、移动客户端(手机、平板电脑等)上运行,还是嵌入到专用设备中?

  • 你的软件是单独安装,还是成批部署?

打包是关于目标环境和部署经验。上面的问题有很多答案,每种情况的组合都有自己的解决方案。有了这些信息,下面的概述将指导你选择最适合你的项目的打包技术。

包装 Python 库和工具

你可能听说过 PyPI、setup.pywheel 文件。这些只是 Python 生态系统提供的向开发者分发 Python 代码的少数工具,你可以在 Packaging and distributing projects 中阅读。

以下的打包方法是针对开发环境中的技术受众所使用的库和工具。如果你正在寻找为非技术用户和/或生产环境打包 Python 的方法,请跳到 包装 Python 应用程序

Python 模块

一个 Python 文件,只要它只依赖于标准库,就可以被重新分发和重复使用。你还需要确保它是为正确的 Python 版本编写的,并且只依赖于标准库。

这非常适合在拥有兼容 Python 版本的人之间分享简单的脚本和片段(比如通过电子邮件、StackOverflow 或 GitHub gists)。甚至有一些完整的 Python 库提供了这个选项,如 bottle.pyboltons

然而,对于由多个文件组成的项目,需要额外的库,或者需要特定版本的 Python,这种模式就无法扩展,因此有了下面的选项。

Python 源码分发

如果你的代码由多个 Python 文件组成,它通常被组织成一个目录结构。任何包含 Python 文件的目录都可以构成一个 Import Package

由于包由多个文件组成,它们更难分发。大多数协议一次只支持传输一个文件(你上次点击一个链接而下载多个文件是什么时候?) 更容易出现不完整的传输,也更难保证目的地的代码完整性”。

只要你的代码只包含纯 Python 代码,而且你知道你的部署环境支持你的 Python 版本,那么你就可以使用 Python 的本地打包工具来创建一个 source Distribution Package,或者简称 sdist

Python 的 sdists 是包含一个或多个软件包或模块的压缩档案(.tar.gz 文件)。如果你的代码是纯 Python 的,而且你只依赖其他 Python 包,你可以 到这里了解更多

如果你依赖任何非 Python 代码,或者非 Python 包(比如 libxml2lxml 的情况下,或者 BLAS 库在 numpy 的情况下),你将需要使用下一节详述的格式,这对纯 Python 库也有很多好处。

备注

Python 和 PyPI 支持多个发行版提供同一软件包的不同实现。例如,未被维护但具有象征意义的 PIL 发行版 提供了 PIL 包,而 Pillow 也是如此,它是 PIL 的一个积极维护的分叉!

这种 Python 打包的超级能力使 Pillow 有可能成为 PIL 的替代品,只需改变你项目的 install_requiresrequirements.txt 即可。

Python 的二进制分发

Python 的大部分实用能力来自于它与软件生态系统整合的能力,特别是用 C、C++、Fortran、Rust 和其他语言编写的库。

并不是所有的开发者都有合适的工具或经验来构建这些用这些编译语言编写的组件,所以 Python 创建了 Wheel,这是一种包格式,旨在用编译的工件来运送库。事实上,Python 的包安装程序 pip 总是更喜欢轮子,因为安装总是更快,所以即使是纯 Python 包也能用轮子更好地工作。

二进制发行版最好是与源代码发行版相匹配。即使你不为每个操作系统上传你的代码轮子,通过上传 sdist,你也可以让其他平台的用户为自己构建它。除非你为一个非常特殊的使用情况创建工件,而你知道接收者只需要其中一个,否则默认为同时发布 sdist 和轮子档案。

Python 和 PyPI 让我们很容易同时上传 wheel 和 sdists。只要按照 包装 Python 项目 的教程就可以了。

A summary of Python's packaging capabilities for tools and libraries.

Python 推荐的内置库和工具打包技术。摘自 包装梯度(2017)

包装 Python 应用程序

到目前为止,我们只讨论了 Python 的本地发布工具。根据我们的介绍,你可以正确地推断出这些内置的方法只针对有 Python 的环境,以及知道如何安装 Python 包的受众。

由于有各种各样的操作系统、配置和人,这种假设只有在针对开发者受众时才是安全的。

Python 的原生包装主要是为在开发者之间分发可重用的代码而建立的,称为库。你可以把 工具,或开发人员的基本应用,放在 Python 的库包装之上,使用像 setuptools entry_points 这样的技术。

库是构建模块,而不是完整的应用程序。对于分发应用程序,有一个全新的技术世界。

接下来的几节将根据这些应用打包选项对目标环境的依赖性来组织它们,这样你就可以为你的项目选择合适的选项。

取决于框架

某些类型的Python应用程序,如网站后端和其他网络服务,已经足够普遍,以至于它们有框架来实现其开发和打包。其他类型的应用程序,如动态 Web 前端和移动客户端,足够复杂,以至于框架变得不仅仅是一种便利。

在所有这些情况下,从框架的打包和部署故事出发,向后工作是有意义的。一些框架包括一个部署系统,它包裹了本指南其余部分所概述的技术。在这些情况下,你会想听从你的框架的打包指南,以获得最简单和最可靠的生产经验。

如果你曾经想知道这些平台和框架在后台是如何工作的,你可以随时阅读后面的章节。

服务平台

如果你正在为 Heroku 或 Google App Engine 这样的 “平台即服务” 或 “PaaS” 进行开发,你将会想要遵循它们各自的打包指南。

在所有这些设置中,平台负责包装和部署,只要你遵循他们的模式。大多数软件并不适合这些模板之一,因此存在以下所有其他选项”

如果你正在开发的软件将被部署到你自己的机器上,用户的个人电脑上,或任何其他安排,请继续阅读。

网络浏览器和移动应用程序

Python 的稳步发展正引领它进入新的空间。如今,你可以用 Python 编写一个移动应用或网络应用前端。虽然语言可能是熟悉的,但打包和部署的做法是全新的”

如果你打算向这些新领域发布,你会想看看以下框架,并参考它们的包装指南:

如果你对使用框架或平台不感兴趣,或者只是想知道上述框架所利用的一些技术和技巧,请继续阅读下文。

取决于预装的 Python

挑选一台任意的计算机,根据环境,很有可能已经安装了 Python。多年来,大多数 Linux 和 Mac 操作系统都默认包含了 Python,你可以合理地依赖 Python 预先存在于你的数据中心或开发者和数据科学家的个人机器上。

支持这种模式的技术:

  • PEX (Python EXecutable)

  • zipapp (does not help manage dependencies, requires Python 3.5+)

  • shiv (requires Python 3)

备注

在这里的所有方法中,依靠预装的 Python 对目标环境的依赖性最大。当然,这也使得软件包最小,小到个位数的兆字节,甚至是千字节。

一般来说,减少对目标系统的依赖会增加我们包的大小,所以这里的解决方案大致上是按照输出大小的增加来排列的。

依赖于独立的软件分发生态系统

很长一段时间以来,许多操作系统,包括 Mac 和 Windows,都缺乏内置的软件包管理。直到最近,这些操作系统才获得了所谓的 “应用商店”,但即使是那些专注于消费者的应用,也没有为开发者提供什么。

开发者长期以来一直在寻求补救措施,在这场斗争中,出现了自己的软件包管理解决方案,如 Homebrew。对于 Python 开发者来说,最相关的替代方案是一个名为 Anaconda 的软件包生态系统。Anaconda 是围绕 Python 建立的,在学术、分析和其他面向数据的环境中越来越普遍,甚至可以 进入面向服务器的环境

关于为 Anaconda 生态系统建立和发布的说明:

类似的模式包括安装另一个 Python 发行版,但不支持任意的操作系统级软件包:

带来你自己的 Python 可执行文件

我们所知道的计算是由执行程序的能力来定义的。每个操作系统都原生支持一种或多种他们可以原生执行的程序格式。

有许多技术和工艺可以把你的 Python 程序变成这些格式之一,其中大多数涉及到把 Python 解释器和任何其他依赖性嵌入到一个可执行文件中。

这种被称为 冻结 的方法提供了广泛的兼容性和无缝的用户体验,尽管通常需要多种技术,以及相当多的努力。

Python 冻结的选择:

上述大部分内容都意味着单用户部署。对于多组件的服务器应用程序,请参阅 Chef Omnibus

携带自己的用户空间

越来越多的操作系统 – 包括 Linux、Mac OS 和 Windows – 可以被设置为运行打包成轻量级镜像的应用程序,使用一种相对现代的安排,通常被称为 操作系统级虚拟化,或 容器化

这些技术大多与 Python 无关,因为它们打包整个操作系统文件系统,而不仅仅是 Python 或 Python 包。

在 Linux 服务器中的采用最为广泛,那里是技术的发源地,也是下面这些技术效果最好的地方:

自带内核

大多数操作系统支持某种形式的经典虚拟化,将应用程序打包成包含其自身完整操作系统的镜像来运行。运行这些虚拟机,或称虚拟机,是一种成熟的方法,在数据中心环境中普遍存在。

这些技术大多保留给数据中心的大规模部署,尽管某些复杂的应用可以从这种包装中受益。技术是与 Python 无关的,包括:

自带硬件

运送你的软件的最全面的方法是把它已经安装在一些硬件上。这样一来,你的软件的用户就只需要用电了。

上述的虚拟机主要是为精通技术的人准备的,而你可以发现从最先进的数据中心到最年轻的孩子都在使用硬件设备。

将你的代码嵌入到 AdafruitMicroPython 或更强大的运行 Python 的硬件上,然后将其运送到数据中心或你的用户家中。他们即插即用,你就可以收工了。

A summary of technologies used to package Python applications.

用于打包 Python 应用程序的简化技术范围。

那么 ……

上面的章节只能总结这么多,你可能想知道一些比较明显的差距。

操作系统包

正如上面的 依赖于独立的软件分发生态系统 中提到的,一些操作系统有自己的软件包管理器。如果你非常确定你的目标操作系统,你可以直接依赖像 deb (用于 Debian,Ubuntu 等)或 RPM (用于 Red Hat,Fedora 等)的格式,并使用该内置软件包管理器来处理安装,甚至部署。你甚至可以使用 FPM 从同一来源生成 deb 和 RPM。

在大多数部署管道中,操作系统包管理器只是拼图中的一个部分。

virtualenv

Virtualenvs 一直是多代 Python 开发者不可或缺的工具,但正在慢慢淡出人们的视线,因为它们正在被更高级别的工具所包裹。 特别是在打包方面,virtualenvs 在 the dh-virtualenv toolosnap 中被作为一种基本工具,这两个工具都以一种独立的方式打包了 virtualenvs。

对于生产部署,不要依赖从互联网上运行 python -m pip install,进入一个 virtualenv,就像人们在开发环境中可能做的那样。上面的概述充满了更好的解决方案。

安全性

你越往下走,就越难更新你的软件包的组件。所有东西都被更紧密地结合在一起。

例如,如果出现了内核安全问题,而你正在部署容器,主机系统的内核可以被更新,而不需要代表应用程序进行新的构建。如果你部署了虚拟机图像,你就需要一个新的构建。这种动态是否使一个选项更安全,仍然是一个有点老的争论,可以追溯到至今仍未解决的 静态与动态链接 的问题。

总结一下吧

Python中的包装有一点名声,那就是一波三折。这种印象主要是 Python 的多功能性的副产品。一旦你理解了每个打包方案之间的自然界限,你就会开始意识到,不同的景观是 Python 程序员为使用最平衡、最灵活的语言之一而付出的小代价。