Python源码包和二进制包(包含打包过程细节讲解)

通常来说,Python 程序包有两种类型的发行版,分别是源代码发行版和二进制(构建)发行版。

源代码发行版是最简单的,也是最不依赖于平台的,对于纯 Python 包,无需动脑选择它就行,这种发行版只包含 Python 源代码,具有高度的可移植性。

但当包中引入了用其他语言(例如 C 语言)编写的一些扩展,如果包用户的环境中有合适的开发工具链(主要包括编译器和正确的 C 头文件)的话,那么源代码发行版也是可行的。相比较而言,二进制发行版的格式可能更适合,因为它可以为特定平台提供已经构建好的扩展。

sdist发行版

sdist 命令是最简单的命令,它创建一棵分发树,其中复制了运行一个包所需要的全部内容,这棵树被归档到一个或多个存档文件中(通常只创建一个 tar 文件),这个存档基本上是源代码树的副本。

这个命令是从目标系统独立地分发一个包的最简单方法,它将创建一个 dist 文件夹,里面包含可被分发的存档。为了能够使用它,必须向 setup 传递一个额外参数以提供版本号,如果没有提供 version 值,那它默认为 version = 0.0.0,代码如下:
from setuptools import setup
setup(
    name='acme.sql',
    version='0.1.1'
)
这个版本号在升级安装时非常有用,因为每次发布包时版本号都会增加,这样目标系统就知道它发生了变化。

我们运行带有这个额外参数的 sdist 命令,代码如下:

$ python setup.py sdist
running sdist
...
creating dist
tar -cf dist/acme.sql-0.1.1.tar acme.sql-0.1.1
gzip -f9 dist/acme.sql-0.1.1.tar
removing 'acme.sql-0.1.1' (and everything under it)
$ ls dist/
acme.sql-0.1.1.tar.gz

在 Windows 中,存档其实就是一个ZIP文档。

版本被用于标记存档名称,这个存档可以在任何拥有 Python 的系统上分发并安装。在 sdist 发行版中,如果包里面包含 C 库或扩展,那么目标系统将负责编译它们。

这在基于 Linux 的系统或 Mac OS 中很常见,因为这些系统通常都会提供编译器,但在 Windows 下却并不常见。因此,如果一个包打算在多个平台中运行,那么分发时应该同时提供预构建的发行版。

bdist和wheels发行版

为了能够分发预构建的发行版,distutils 提供了 build 命令,可以通过 4 个步骤来编译包,分别是:
  1. build_py:通过字节编译并将其复制到构建文件夹中,来构建纯 Python 模块。
  2. build_clib:如果包中包含任何 C 库,它会利用 C 编译器在构建文件夹中创建一个静态库来构建C库。
  3. build_ext:构建 C 扩展,并像 build_clib 一样将结果放在构建文件夹中。
  4. build_scripts:构建被标记为脚本的模块。如果第一行被设为 !# 的话,它还会修改解释器路径并修改文件模式使其变为可执行文件。
上面每个步骤都是可以被单独调用的命令,编译过程的结果是构建一个文件夹,里面包含要安装的包所需要的全部内容。

需要注意的是,distutils 包中还没有提供交叉编译器的选项,也就是说,这些命令的结果总是针对构建时所使用的操作系统。

如果必须创建一些 C 扩展,构建过程将使用系统编译器和 Python 头文件(Python.h)。Python 从源代码构建完成之后这个包含(include)文件就是可用的了。对于打包的发行版,可能需要针对系统发行版的额外包,至少在流行的 Linux 发行版中,它通常被命名为 python-dev,其包含构建 Python 扩展所有必要的头文件。

所使用的 C 编译器是系统编译器,对于基于 Linux 系统或 Mac OS X 而言,它分别是 gcc 或 clang。对于 Windows 而言,可以使用 Microsoft Visual C++,也可以使用开源项目 MinGW,可以在 distutils 中进行相应的配置。

bdist 命令使用 build 命令来构建二进制发行版。它调用 build 和所有依赖的命令,然后用和 sdist 相同的方式创建一份存档。

我们在 Mac OS X 系统中为 acme.sql 创建一个二进制发行版,如下所示:

$ python setup.py bdist
running bdist
running bdist_dumb
running build
...
running insta1l_scripts
tar -cf dist/acme.sql-0.1.1.macosx-10.3-fat.tar
gzip -f9 acme.sql-0.1.1.macosx-10.3-fat.tar
removing 'build/bdist.macosx-10.3-fat/dumb' (and everything under it)

$ ls dist/
acme.sql-0.1.1.macosx-10.3-fat.tar.gz acme.sql-0.1.1.tar.gz

注意,新创建的存档名称中包含系统名称及其发行版本(Mac OS X 10.3)。


在 Windows 中,调用相同的命令将会创建一个特定的发行版存档,如下所示:

C:\acme.sql> python.exe setup.py bdist
...
C:\acme.sql> dir dist
25/02/2008 08:18 <DIR>          .
25/02/2008 08:18 <DIR>          ..
25/02/2008 08:24                16 055 acme.sql-0.1.Win32.zip
              1  File(s)                        16 055 bytes
              2   Dir(s)          22 239 752 192 bytes free

如果一个包里包含 C 代码,那么除了源代码发行版之外,发布尽可能多的不同的二进制发行版也很重要。至少对于那些没存安装 C 编译器的人是很重要的。

二进制版本中包含一棵可以汽接复制到 Python 树中的树,它主要包含一个文件夹,将被复制到 Python 的 site-packages 文件夹中。同时,它还可能包含缓存字节码文件(在 Python 2.x 中是 *.pyc 文件,在 Python 3.x 中是 __pycache__/*.pyc)。

另一种构建发行版是 wheel 包提供的“wheel”,安装完 wheel 后(例如使用 pip),它会向 distutils 中添加一个新的 bdist_wheel 命令。wheel 允许创建特定平台的发行版(目前仅适用于 Windows 和 Mac OS X),作为普通 bdist 发行版的替代。

设计 wheel 是为了替代早先 setuptools 引入的另一种 egg 版本,egg 现在己经过时了,所以这里不再详细介绍它。

使用 wheel 的优点相当多,在 Python Wheels 中提到的优点如下所示:
  • 更快速地安装纯 Python 包和本地 C 扩展包;
  • 避免安装任意代码执行(避免 setup.py);
  • 安装 C 扩展不需要 Windows 或 OS X 上的编译器。
  • 允许更好的缓存,用于测试和持续集成。
  • 创建 .pyc 文件作为安装的一部分,以确保它们匹配所使用的 Python 解释器;
  • 在跨平台和跨机器上更一致的安装。

根据 PyPA 的推荐,wheel 应该成为默认的分发格式。但 Linux 平台特定的 wheel 还不可用,因此如果必须分发带有 C 扩展的包,那么需要为 Linux 用户创建 sdist 发行版。