PyInstaller 简介

想要将 Python 程序分发给没有 Python 的客户?PyInstaller 就是答案。

如果您习惯使用编译型语言,那么需要安装编程语言才能运行应用程序的概念可能有点奇怪。仅仅因为程序是用 C 语言编写的,并不意味着您需要 C 编译器才能运行它,对吧?

但当然,解释型和字节编译型语言确实需要原始语言或其版本才能运行。没错,Java 程序是编译的,但它们被编译成字节码,然后由 JVM 执行。类似地,除非存在 CLR,否则 .NET 程序无法运行。

即便如此,我的 Python 课程中的许多学生仍然惊讶地发现,如果您想运行 Python 程序,则需要安装 Python 语言。如果您运行的是 Linux,这不是问题。自 1995 年以来,我使用的每个发行版都附带了 Python。有时 Python 版本不如我希望的那么新,但“这台计算机无法运行 Python 程序”的概念并不是我经常需要处理的事情。

但是,并非每个人都运行 Linux,也并非每台计算机都安装了 Python。您能对此做些什么呢?更具体地说,当您的客户没有 Python 并且对安装它不感兴趣时,您能做些什么呢?或者,如果您只是想用 Python 编写和分发应用程序,而不必让用户为额外的安装要求而烦恼,那又该怎么办呢?

在本文中,我将讨论 PyInstaller,这是一种跨平台工具,可让您获取 Python 程序并将其分发给用户,以便他们可以将其视为独立应用程序。我还将讨论它不能做什么,因为许多考虑使用 PyInstaller 的人并不完全理解它能做什么和不能做什么。

运行 Python 代码

与 Java 和 .NET 一样,Python 程序被编译成字节码,这是一种高级命令,它不对应于任何实际计算机的指令,而是引用称为“虚拟机”的东西。但是,Java 和 Python 之间存在许多实质性差异。Python 没有显式的编译阶段;它的字节码级别相当高,并且与 Python 语言本身相关联,并且编译器在优化方面做得不多。Python 源代码和生成的字节码之间的对应关系基本上是一对一的;您不会发现字节码编译器执行诸如内联代码或优化循环之类的花哨操作。

但是,毫无疑问,Python 运行的是字节码,而不是您的源代码。您可以通过多种不同的方式看到这一点,最简单的方法是创建一个 Python 模块,然后导入该模块。该模块被翻译成 Python 字节码,然后保存到带有 .pyc 后缀的文件中。(在 Python 3 中,这位于名为 __pycache__ 的目录中,针对不同的 Python 版本和架构具有单独的字节编译版本。)

所有这些与 PyInstaller 有什么关系?嗯,如果您想分发 Python 程序,仅仅提供字节编译的输出是不够的。您还需要提供 Python 的副本,事实证明,在某些情况下,这很麻烦,正如我之前提到的。

PyInstaller 获取您的 Python 代码并对其进行字节编译。但是,然后它还会创建一个可执行应用程序,该应用程序基本上加载 Python 并运行您的程序。换句话说,您使用 PyInstaller 分发的每个应用程序都包含 Python 的完整副本,包括运行程序所需的库。通常,Python 包括整个标准库,但 PyInstaller 足够智能,只包含真正需要的模块,从而将分发大小控制在合理范围内。

请注意,使用 PyInstaller 时,您拥有的 Python 副本用于创建可分发包。这意味着如果您在 Linux 上运行 Python 3.4,那么将包含在您的包中的是该 Linux 版 Python 3.4。换句话说,PyInstaller 跨平台工作,因为您可以在 Linux、Windows、macOS 和其他系统上运行它,但生成的包专门用于一种架构。这也意味着当您在安装了多个 Python 版本的计算机上使用 PyInstaller 时,您需要格外小心。

安装 PyInstaller

PyInstaller 最容易在运行 Python 的计算机上使用标准 pip 命令安装


pip install -U --user pyinstaller

-U 标志表示您想要升级 PyInstaller,以防您已经安装了它并且 PyPI 上的版本较新。--user 标志表示您不想将其安装在系统目录中,而是安装在您自己的主目录下。最近,我越来越喜欢使用 --user 安装东西,主要是因为它避免了考虑权限的需要。但是,这确实意味着您需要将 --user 位置的“bin”目录添加到您的 PATH 中。

如果您使用的计算机安装了多个 Python 版本,有时可能很难知道哪个版本连接到 pip。(尽管 pip --version 会告诉您它正在使用的 Python 版本。)因此,我有时会采用更长的方法,如下所示


python3.6 -m pip install -U --user pyinstaller

-m 标志有点像 Python 中的 import 语句;以这种方式运行可以确保您使用的是所需的版本。

现在您已经安装了 PyInstaller,让我们使用它来创建一个可分发的 Python 应用程序。我创建了一个名为 myapp.py 的新程序(非常有创意)。这是源代码


#!/usr/bin/env python3.6

import sys

print("Hello, and welcome to my app!")

print(f"We're running Python {sys.version}")

for i in range(10):
    print(f"{i} ** 2 = {i**2}, {i} ** 3 = {i**3}")

如您所见,此程序导入了 sys 模块,该模块提供对 Python 环境及其变量和设置的访问。我这样做是为了获取 sys.version 并确保正确的版本确实在运行。

接下来,我执行一个“for”循环,原因仅仅是为了给我一些在程序运行时可以在屏幕上看到的输出。

在这两种情况下,我都使用了 Python 3.6 中我最喜欢的功能之一,f-strings,它允许我在花括号内插值表达式。在我看来,这比以前在 Python 中使用“%”运算符在字符串上或(最近)使用 str.format 方法完成此操作的方式要好得多。

因此,假设您想在同事的机器上运行此程序。(请记住,您的同事需要运行与您相同的操作系统,因为 PyInstaller 的输出将是基于您安装的 Python 版本的二进制文件。)您可以键入


pyinstaller myapp.py

并且,您将获得大量输出。我不会回顾所有内容,但以下是一些亮点


468 INFO: PyInstaller: 3.3.1
468 INFO: Python: 3.6.3
470 INFO: Platform:
 ↪Linux-4.4.0-119-generic-x86_64-with-Ubuntu-16.04-xenial
475 INFO: wrote /root/myapp1/myapp.spec

文件“myapp.spec”描述了您正在使用 PyInstaller 创建的应用程序。您会发现此文件在您运行 PyInstaller 时会自动创建。通常,PyInstaller 足够智能,可以找出必须包含在结果分发中的文件,但在某些情况下,例如数据文件和共享库,您可能必须编辑 specfile 并自行添加它们


491 INFO: Extending PYTHONPATH with paths
['/root/myapp1', '/root/myapp1']

当您在 Python 中说 import xyz 时,该语言(首先)在当前目录中查找“xyz.py”。如果找不到(或字节码变体),它会逐个查找 sys.path 的元素,查找“xyz.py”。如果您想告诉 Python 在一些额外的目录中查找,您可以设置 PYTHONPATH 环境变量。在这里,PyInstaller 表示它正在修改 PYTHONPATH,以便程序可以找到在当前目录中定义的模块和包


491 INFO: checking Analysis

PyInstaller 分析您的代码,以找出您要使用的模块和包。使用的模块包含在最终分发中,而未使用的模块将被忽略。

“dist”目录

输出还有很多,但在运行 PyInstaller 后,您会发现有一个“dist”目录,并且在该目录中还有另一个子目录,其名称是您的新应用程序的名称。

此目录包含您的新 Python 应用程序。现在,您不能像那样直接运行它;它仍然比您一般的可执行文件复杂一些。想法是将目录变成 zipfile,将软件分发到您需要的任何地方,在目标机器上解压缩它,然后运行顶层程序。

但是,如果您使用的模块不仅具有 Python 组件,而且还具有编译后的 C 组件怎么办?PyInstaller 会自动处理这种情况。例如,假设您要在程序中使用 NumPy,PyInstaller 如何处理编译后的 C 部分?

在这种情况下,PyInstaller 注意到您正在使用带有 C 组件的模块。如果您查看“dist”目录,您现在会看到一堆额外的共享库(*.so 文件)。

PyInstaller 不能保证与所有复杂包兼容,但作者已努力提供高度兼容性。例如,如果您使用 Cython 包(用于在 C 中实现 Python 模块或提供类型提示),您会发现 PyInstaller 可以很好地处理它,包括“dist”目录中的相应文件。

结论

多年来,我的许多学生和咨询客户都希望分发 Python 代码,而无需运行语言本身。这是不可能的,但 PyInstaller 做到了次好的事情,让您以相当直接的方式分发软件。

Reuven Lerner 在世界各地的公司教授 Python、数据科学和 Git。您可以订阅他的免费每周“更好的开发者”电子邮件列表,并在他的书籍和课程中学习,网址为 http://lerner.co.il。Reuven 与他的妻子和孩子住在以色列的莫迪因。

加载 Disqus 评论