在介绍 Python 代码打包的材料中,创建独立可执行文件是经常被忽略的一个主题。这
主要是因为 Python 标准库中缺少合适的工具能够让程序员创建简单的可执行文件,用户不
需要安装 Python 解释器就可以运行这些可执行文件。
与 Python 相比,编译语言有一个很大的优点,就是它允许为给定的系统架构创建可执
行的应用程序,用户不需要知道底层技术就可以运行。Python 代码作为一个包分发时,需
要有 Python 解释器才能运行。这对于没有足够技术水平的用户来说造成了很大不便。
对开发者友好的操作系统(例如 Mac OS X 或大多数 Linux 发行版)都预装了 Python。
因此对于它们的用户来说,基于 Python 的应用仍然可以作为源代码包分发,依赖于主脚本
文件中特定的解释器指令(interpreter directive),这一指令通常被称为 shebang。对于大多数 Python 应用而言,其格式如下所示:
#!/usr/bin/env python
这种指令放在脚本的第一行,会将其标记为默认由指定环境的 Python 版本进行解释。
当然,它也可以采用更详细的形式,其中包括特定的 Python 版本,例如 python 3.4、
python 3 或 python 2。注意,这适用于大多数 POSIX 系统,但从定义来看并不是可移
植的。这种解决方案依赖于特定 Python 版本的存在,也依赖于/usr/bin/env 的 env 可
执行文件的可用性。在某些操作系统中,这两个假设可能都无法满足。此外,shebang 在
Windows 上也无法使用。另外,即使是有经验的开发人员,Windows 中 Python 环境的引导
也是一项挑战,所以你不能期望非技术用户能够自己完成。
另一件需要考虑的事情是桌面环境中简单的用户体验。用户通常期望,只通过单击就
可以从桌面运行应用程序。并不是所有桌面环境都支持将 Python 应用作为源代码分发。
因此,我们最好能够创建一个二进制发行版,其使用方法与其他任何编译的可执行文
件相同。幸运的是,可以创建一个可执行文件,里面同时嵌入了 Python 解释器和我们的项
目。这样用户无需考虑 Python 或其他依赖就可以打开我们的应用。
独立可执行文件何时有用
如果用户体验的简单性比用户与应用代码交互的能力更加重要,那么独立可执行文件
非常有用。注意,将应用作为可执行文件分发,只会使代码读取或修改更加困难—但不
是不可能。这不是保护应用代码的方法,而是应该作为使应用交互更加简单的方法。
独立可执行文件应该是对非技术最终用户分发应用的首选方式,也可能是分发
Windows 上的 Python 应用的唯一合理方式。
独立可执行文件通常适用于以下情形。
• 依赖于特定 Python 版本的应用,该版本在目标操作系统是可能不容易找到。
• 依赖于修改过的预编译 CPython 源代码的应用。
• 带有图形界面的应用。
• 具有许多用不同语言编写的二进制扩展的应用。
• 游戏。
常用工具
Python 没有任何内置库支持构建独立可执行文件。幸运的是,一些社区项目解决了这
一问题,并取得了不同程度的成功。其中最有名的 4 个是:
• PyInstaller;
• cx_Freeze;
• py2exe;
• py2app。
它们中的每一个在使用上都略有不同,每一个也都受到稍微不同的限制。在选择工具
之前,你需要确定面向哪些平台,因为每种打包工具仅支持特定的一些操作系统。
最好的情况是,在项目周期的最开始做出这样的决定。当然,所有这些工具都不需要
在代码中进行深入的交互,但如果你早期就开始构建独立包,那么你可以将整个过程自动
化,并节省未来的集成时间与成本。如果你将这件事放到以后,你可能会发现自己处于这
样的境地:项目构建得过于复杂,以致于所有可用的工具都无法使用。为这样的项目提供
独立可执行文件是有问题的,而且需要大量的时间。
1.PyInstaller
到目前为止,PyInstaller(http://www.pyinstaller.org/)是将 Python 包冻结为独立可执行
文件的最先进的程序。它在目前每种可用的解决方案中提供最广泛的多平台兼容性,所以
它也是最受推荐的方法。PyInstaller 支持的平台包括:
• Windows(32 位和 64 位);
• Linux(32 位和 64 位);
• Mac OS X(32 位和 64 位);
• FreeBSD、Solaris 和 AIX。
支持的 Python 版本包括 Python 2.7 与 Python 3.3、3.4 和 3.5。它可以在 PyPI 上找到,
所以可以利用 pip 在工作环境中安装它。如果你用这种安装方法时遇到问题,你可以随时
从项目主页上下载安装程序。
不幸的是,它不支持跨平台构建(交叉编译),因此如果你想要为某个特定平台构建独
立可执行文件,那么你需要在那个平台上执行构建。随着许多虚拟化工具的出现,这在今
天并不是一个大问题。如果你的计算机上没有安装某个特定的系统,你可是随时使用
Vagrant,它会为你提供所需要的操作系统作为虚拟机。
简单应用的用法很简单。假设我们的应用包含在名为 myscript.py 的脚本中。这是一个
简单的"Hello world!"应用。我们想要为 Windows 用户创建一个独立可执行文件,我们的源代码
位于文件系统的 D://dev/app 目录下。利用下面这个简短的命令可以将我们的应用打包:
$ pyinstaller myscript.py
2121 INFO: PyInstaller: 3.1
2121 INFO: Python: 2.7.10
2121 INFO: Platform: Windows-7-6.1.7601-SP1
2121 INFO: wrote D:\dev\app\myscript.spec
2137 INFO: UPX is not available.
2138 INFO: Extending PYTHONPATH with paths
['D:\\dev\\app', 'D:\\dev\\app']
2138 INFO: checking Analysis
2138 INFO: Building Analysis because out00-Analysis.toc is non
existent
2138 INFO: Initializing module dependency graph...
2154 INFO: Initializing module graph hooks...
2325 INFO: running Analysis out00-Analysis.toc
(...)
25884 INFO: Updating resource type 24 name 2 language 1033
即使是简单的应用,PyInstaller 的标准输出也相当长,所以为了简洁起见,上一个例子
只截取了一部分。如果在 Windows 上运行,生成的目录和文件结构如下所示:
$ tree /0066
│ myscript.py
│ myscript.spec
│
├───build
│ └───myscript
│ myscript.exe
│ myscript.exe.manifest
│ out00-Analysis.toc
│ out00-COLLECT.toc
│ out00-EXE.toc
│ out00-PKG.pkg
│ out00-PKG.toc
│ out00-PYZ.pyz
│ out00-PYZ.toc
│ warnmyscript.txt
│
└───dist
└───myscript
bz2.pyd
Microsoft.VC90.CRT.manifest
msvcm90.dll
msvcp90.dll
msvcr90.dll
myscript.exe
myscript.exe.manifest
python27.dll
select.pyd
unicodedata.pyd
_hashlib.pyd
dist/myscript 目录包含构建应用,现在可以分发给用户。注意,必须分发整个目
录。它包含运行应用所需要的所有附加文件(DLL、编译扩展库等)。利用 pyinstaller
命令的--onefile 开关可以得到更紧凑的发行版,如下所示:
$ pyinstaller --onefile myscript.py
(...)
$ tree /f
├───build
│ └───myscript
│ myscript.exe.manifest
│ out00-Analysis.toc
│ out00-EXE.toc
│ out00-PKG.pkg
│ out00-PKG.toc
│ out00-PYZ.pyz
│ out00-PYZ.toc
│ warnmyscript.txt
│
└───dist
myscript.exe
如果使用--onefile 选项进行构建,你唯一需要向用户分发的文件就是 dist 目录中
找到的单一可执行文件(这里是 myscript.exe)。对于小型应用来说,这可能是首选选项。
运行 pyinstaller 命令的一个副作用是创建了*.spec 文件。这是一个自动生成的
Python 模块,其中包含如何从源代码创建可执行文件的说明。例如,我们已经在下面的代
码中使用了这个:
# -*- mode: python -*-
block_cipher = None
a = Analysis(['myscript.py'],
pathex=['D:\\dev\\app'],
binaries=None,
datas=None,
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
name='myscript',
debug=False,
strip=False,
upx=True,
console=True )
这个.spec 文件包含前面说过的所有 pyinstaller 参数。如果你对构建执行了大量
自定义,那么这一点是非常有用的,因为它可以用来替代保存配置的构建脚本。一旦创建
完成后,你可以用它而不是 Python 脚本作为 pyinstaller 命令的参数,如下所示:
$ pyinstaller.exe myscript.spec
注意,这是一个真正的 Python 模块,因此你可将其扩展,并利用你已经熟悉的语言对
构建过程执行更复杂的自定义。如果你面向许多不同的平台,那么自定义.spec 文件特别
有用。此外,不是所有的 pyinstaller 选项都可以通过命令行参数使用,只有在修
改.spec 文件时可以使用。
PyInstaller 是一个扩展工具,它对于绝大多数程序的用法都非常简单。无论如何,如果
你有兴趣用它作为分发应用的工具,那么推荐你深入阅读它的文档。
2.cx_Freeze
cx_Freeze(http://cx-freeze.sourceforge.net/)是另一种用于创建独立可执行文件的工具。
它是一种比 PyInstaller 更加简单的解决方案,但也支持 3 个主要平台:
• Windows;
• Linux;
• Mac OS X。
与 PyInstaller 一样,它不允许我们执行跨平台构建,因此你需要在想要分发的同一个
操作系统中创建可执行文件。cx_Freeze 的主要缺点是它不允许我们创建真正的单文件可执
行文件。用它构建的应用都需要与相关的 DLL 文件和库一起分发。假如我们想要创建与
PyInstaller 一节介绍过的相同的应用,示例用法也非常简单,如下所示:
$ cxfreeze myscript.py
copying C:\Python27\lib\site-packages\cx_Freeze\bases\Console.exe ->
D:\dev\app\dist\myscript.exe
copying C:\Windows\system32\python27.dll ->
D:\dev\app\dist\python27.dll
writing zip file D:\dev\app\dist\myscript.exe
(...)
copying C:\Python27\DLLs\bz2.pyd -> D:\dev\app\dist\bz2.pyd
copying C:\Python27\DLLs\unicodedata.pyd ->
D:\dev\app\dist\unicodedata.pyd
生成的文件结构如下所示:
$ tree /f
│ myscript.py
│
└───dist
bz2.pyd
myscript.exe
python27.dll
unicodedata.pyd
cx_Freeze 扩展了 distutils 包,而不是为构建规范提供自己的格式(像 PyInstaller 那
样)。也就是说,你可以使用熟悉的 setup.py 脚本来配置如何构建独立可执行文件。如果
你已经使用 setuptools 或 distutils 来分发包的话,那么使用 cx_Freeze 非常方便,因
为额外的集成只需要对 setup.py 脚本进行很小的修改。下面是这种 setup.py 脚本的一
个示例,利用 cx_Freeze.setup()在 Windows 上创建独立可执行文件,如下所示:
import sys
from cx_Freeze import setup, Executable
# 自动检测依赖。但可能需要微调。
build_exe_options = {"packages": ["os"], "excludes": ["tkinter"]}
setup(
name="myscript",
version="0.0.1",
description="My Hello World application!",
options={
"build_exe": build_exe_options
},
executables=[Executable("myscript.py")]
)
利用这样一个文件,可以向 setup.py 脚本添加新的 build_exe 命令来创建新的可
执行文件:
$ python setup.py build_exe
cx_Freeze 的用法似乎比 PyInstaller 更简单一些,而且 distutils 集成是一个非常有
用的功能。不幸的是,这个项目可能会给没有经验的开发者带来一些麻烦。
• 在 Windows 中利用 pip 安装可能会有问题。
• 官方文档非常简短,有些地方还是缺失的。
3.py2exe 和 py2app
py2exe(http://www.py2exe.org/)和 py2app(https://pythonhosted.org/py2app/)是另外
两种用于创建独立可执行文件的程序,通过 distutils 或 setuptools 与 Python 打包
进行集成。这里将它们二者放在一起,是因为它们的用法和限制都非常相似。py2exe 和
py2app 的主要缺点是它们只面向一种平台。
• py2exe 允许构建 Windows 可执行文件。
• py2app 允许构建 Mac OS X 应用。
由于用法非常相似,并且只需要修改 setup.py 脚本,这两个包似乎是互补的。py2app
项目的文档中给出了 setup.py 脚本的以下示例,可以根据所使用的平台选择正确的工具
(py2exe 或 py2app)来构建独立可执行文件,如下所示:
import sys
from setuptools import setup
mainscript = 'MyApplication.py'
if sys.platform == 'darwin':
extra_options = dict(
setup_requires=['py2app'],
app=[mainscript],
# Cross-platform applications generally expect sys.argv to
# be used for opening files.
options=dict(py2app=dict(argv_emulation=True)),
)
elif sys.platform == 'win32':
extra_options = dict(
setup_requires=['py2exe'],
app=[mainscript],
)
else:
extra_options = dict(
# Normally unix-like platforms will use "setup.py install"
# and install the main script as such
scripts=[mainscript],
)
setup(
name="MyApplication",
**extra_options
)
有了这样的脚本,你可以利用 python setup.py py2exe 命令构建 Windows 可执
行文件,也可以利用 python setup.py py2app 构建 Mac OS X 应用。交叉编译当然是
不可能的。
尽管有一些限制,并且灵活性不如PyInstaller或cx_Freeze,但最好知道py2exe和py2app
项目的存在。在某些情况下,PyInstaller 或 cx_Freeze 可能无法正确构建项目的可执行文件。
在这种情况下,检查其他解决方案能否处理我们的代码总是值得的。