首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

使用PyInstaller轻松分发Python应用程序

你是否嫉妒Go开发人员构建了一个可执行文件并轻松地将其交付给用户?如果你的用户可以在不安装任何东西的情况下运行你的应用程序,那不是很棒吗?这是一个梦想,而PyInstaller是在Python生态系统中实现这个梦想的一种方法。

有无数关于如何设置虚拟环境、管理依赖项和发布到PyPI的教程,这在创建Python库时非常有用。针对开发人员构建Python应用程序的信息要少得多。本教程适用于希望将应用程序分发给用户的开发人员,用户可能是Python开发人员也可能不是。

在本教程中,你将学习以下内容:

PyInstaller如何简化 应用 程序分发

如何在你自己的项目中使用 PyInstaller

如何调试PyInstaller错误

PyInstaller不能做什么事情

PyInstaller使你能够创建一个文件夹或可执行文件,用户无需任何额外安装就可以立即运行该文件夹或可执行文件。为了充分了解PyInstaller的强大功能,有必要回顾一下PyInstaller帮助你避免的一些分发问题。

分发问题

设置Python项目可能会令人沮丧,尤其是对非开发人员来说。通常,设置从打开一个终端开始,这对于大量的潜在用户来说是不可能的。在安装指南并深入研究虚拟环境、Python版本和大量潜在依赖关系的复杂细节之前,这个障碍就已经阻止了用户。

在为Python开发设置一个新机器时,请考虑一下你通常要经历的事情。大概是这样的:

下载并安装 一 个 特定的Python版本

设置pip

设置虚拟环境

得到代码的副本

安装依赖项

如果你不是开发人员,请停下来考虑一下上面的步骤是否有意义,Python开发人员就更不用说了。可能不会停下来考虑。

如果你的用户足够幸运地到达了安装的依赖项部分,这些问题就会爆发。在过去的几年里,随着wheel的流行,这一点已经变得更好了,但是一些依赖项仍然需要C/C++,甚至是FORTRAN编译器!

如果你的目标是让尽可能多的用户可以使用应用程序,那么这个要求就太高了。正如Raymond Hettinger在他精彩的演讲中经常说的:“一定有一个更好的方法。”

PyInstaller

PyInstaller通过查找所有这些依赖项并将它们绑定在一起,从用户那里抽象出这些细节。你的用户甚至不知道他们正在运行一个Python项目,因为Python解释器本身被绑定到你的应用程序中。再见了,复杂的安装说明!

PyInstaller通过自省你的Python代码,检测依赖项,然后根据你的操作系统将它们打包成一个合适的格式,从而实现这一惊人的功能。

PyInstaller有很多有趣的细节,但现在你将了解它的基本工作原理以及如何使用它。如果你想了解更多细节,你可以参考优秀的PyInstaller文档。

此外,PyInstaller还可以为Windows、Linux或macOS创建可执行程序。这意味着Windows用户将获得一个.exe,Linux用户将得到一个常规的可执行文件,macOS用户将得到一个.app包。这一功能还是有一定的限制。了解更多信息,请参见限制部分。

准备你的项目

PyInstaller要求你的应用程序要遵循一些最小的结构,即有一个CLI脚本来启动你的应用程序。通常,这意味着在Python包之外创建一个小脚本,该脚本只导入你的包并运行main()。

入口点脚本是一个Python脚本。从技术上来说,你可以在入口点脚本中做任何你想做的事情,但是你应该避免使用显式的相对导入。如果你的首选样式是相对导入,那么你仍然可以在应用程序的其余部分中使用相对导入。

注意: 入口点是启动项目或应用程序的代码。

你可以在自己的项目中进行尝试,或者跟随真正的Python阅读器项目。有关阅读器项目的更详细信息,请参阅《关于在PyPI上发布包的教程》。

构建此项目的可执行版本的第一步是添加入口点脚本。幸运的是,阅读器项目结构良好,所以你只需要在包外编写一个简短的脚本来运行它。例如,你可以在reader包旁边创建一个名为cli.py的文件,并添加以下代码:

这个cli.py脚本会调用main()来启动阅读器。

当你在自己的项目中工作时,创建这个入口点脚本非常简单,因为你对代码比较熟悉。然而,要找到其他人代码的入口点并不容易。在这种情况下,你可以从查看第三方项目中的setup.py文件开始。

在项目的setup.py中查找对entry_points参数的引用。例如,这里是此阅读器项目的setup.py:

可以看到,入口点cli.py脚本调用了与entry_points参数中提到函数的相同函数。

更改之后,此阅读器项目目录应该是这样的,假设你将其签出到一个名为reader的文件夹中:

注意,阅读器代码本身没有做任何更改,只增加了一个名为clip .py的新文件。通过PyInstaller使用你的项目通常只需要这个入口点脚本。

但是,你还需要查找函数中使用的__import__()或导入。这些在PyInstaller术语中称为隐藏导入。

如果更改应用程序中的导入太难,你可以手动指定隐藏导入来强制PyInstaller包括这些依赖项。在本教程的后面,你将看到如何做到这一点。

一旦可以在你的包外使用一个Python脚本启动你的应用程序,就可以尝试使用PyInstaller创建可执行文件了。

使用 PyInstaller

第一步是从PyPI安装PyInstaller。你可以像安装其他Python包一样使用pip来实现这一点:

pip将安装PyInstaller的依赖项以及一个新命令:PyInstaller。你可以将PyInstaller导入你的Pyhton代码中并作为一个库使用,但是你可能只能将它作为一个CLI工具使用。

如果你创建了自己的钩子文件,你将可以使用库接口。

如果你只有纯Python依赖项,那么你将增加PyInstaller默认创建可执行文件的可能性。但是,如果你有更多复杂的C/ C++扩展的依赖项,也不要过于担心。

PyInstaller支持许多流行的包,比如NumPy、PyQt和Matplotlib,而不需要你做任何额外的工作。通过参考PyInstaller文档,你可以了解更多关于PyInstaller官方支持的包列表的信息。

如果你的一些依赖项没有在官方文档中列出,也不要担心。许多Python包都可以很好地工作。实际上,PyInstaller非常流行,以至于许多项目都有关于如何使用PyInstaller的说明。

简而言之,你的项目开箱即用的可能性很高。

要尝试创建具有所有默认值的可执行文件,你只需给PyInstaller提供你的主入口点脚本的名称即可。

首先,cd切换到包含你的入口点的文件夹中,并将其作为一个参数传递给pyinstaller命令,该命令会在你安装PyInstaller时被添加到你的PATH中。

例如,如果你正在跟进阅读器项目,在cd到阅读器项目的顶层目录之后键入以下命令:

如果你在构建可执行文件时看到大量输出,不要惊慌。PyInstaller在默认情况下是冗长输出的,并且冗长程度可以在调试时调高,稍后你将看到这一点。

深入PyInstaller构件

PyInstaller的内部比较复杂,会产生很多输出。所以,知道首先要关注什么是很重要的。也就是说,你可以将可执行文件和潜在的调试信息分发给用户。默认情况下,pyinstaller命令会创建一些有趣的东西:

一个*.spec 文件

一个 build/ 文件夹

一个dist/ 文件夹

spec文件

默认情况下,spec文件将以你的CLI脚本命名。按照前面的示例,你将看到一个名为cli.spec的文件。下面是在clil .py文件上运行PyInstaller后的默认spec文件:

这个文件将由pyinstaller命令自动创建。你的版本可能会有不同的路径,但大多数应该是相同的。

不要担心,你不需要理解上面的代码就可以有效地使用PyInstaller!

这个文件可以被修改并重用,以便在稍后创建可执行文件。通过向pyinstaller命令提供这个spec文件代替入口点脚本,你可以使将来的构建更快一些。

PyInstaller spec文件有几个特定的用例。然而,对于简单的项目,你不需要担心这些细节,除非你想大量自定义你的项目的构建方式。

Build文件夹

build/文件夹是PyInstaller放置用于构建可执行文件的大部分元数据和内部簿记的地方。默认内容如下:

build文件夹对于调试非常有用,但是除非你遇到问题,否则这个文件夹在很大程度上可以被忽略。在本教程的后面,你将学习更多关于调试的知识。

Dist文件夹

构建完成后,你将得到一个与下面类似的dist/文件夹:

dist/文件夹包含你希望交付给用户的最终作品。在dist/文件夹中,有一个文件夹以你的入口点命名。因此,在本例中,你将得到一个dist/cli文件夹,其中包含我们应用程序的所有依赖项和可执行文件。如果你是在Windows中,要运行的可执行文件是dist/cli/cli或dist/cli/cli.exe。

你还会发现许多扩展名为.so、.pyd和.dll的文件,具体取决于你的操作系统。这些共享库表示PyInstaller创建和收集的项目的依赖项。

注意: 如果你是使用git进行版本控制的话,你可以向你的.gitignore文件添加*.spec,、build/ 和 dist/来保持你的git status清晰。Python项目的默认GitHub gitignore文件已经为你做到了这一点。

你需要分发整个dist/cli文件夹,但是你可以将cli重命名为任何适合你的名称。

此时,如果你正在跟随阅读器示例,那你就可以尝试运行dist/cli/cli可执行文件。

你将注意到,运行此可执行文件会导致提到version.txt文件的错误。这是因为阅读器及其依赖项需要一些PyInstaller不知道的额外数据文件。要解决这个问题,你必须告诉PyInstaller version.txt是必需的,你将在测试新可执行文件时学习到这一点。

自定义你的构建

PyInstaller附带了许多选项,可以作为spec文件或常规CLI选项提供。下面,你将发现一些最常见和有用的选项。

—name

||| 更改可执行文件的名称。

这是一种避免你的可执行文件、spec文件和构建工件@,@@@文件夹以你的入口点脚本命名的方法。如果你习惯将入口点脚本命名为cli.py之类的名称,和我一样,那么--name选项是非常有用的。

你可以使用这样一个命令从cli.py脚本构建一个名为realpython的可执行文件:

--onefile

||| 将你的整个应用程序打包到一个可执行文件中。

默认选项会创建一个包含依赖项和可执行文件的文件夹,而--onefile则只创建一个可执行文件,从而使分发更加容易。

此选项不接受任何参数。要将你的项目捆绑到一个文件中,你可以使用如下命令构建:

使用上面的命令,你的dist/文件夹将只包含一个可执行文件,而不是将所有文件放在不同的文件中的一个文件夹。

--hidden-import

||| 列出PyInstaller无法自动检测到的多个顶级导入包。

这是使用函数内部的import和__import__()处理你的代码的一种方法。你还可以在同一个命令中多次使用--hidden-import选项。

此选项需要你希望包含在可执行文件中的包的名称。例如,如果你的项目在一个函数中导入了requests库,那么PyInstaller将不会自动将requests库包含在你的可执行文件中。你可以使用以下命令强制包含requests库:

你可以在你的build命令中多次指定这一点,为每个隐藏导入各指定一次。

--add-data 和 --add-binary

||| 指示PyInstaller将其它数据或二进制文件插入到你的构建中。

当你希望将配置文件、示例或其它非代码数据捆绑在一起时,这非常有用。如果你正在跟进此feed reader项目,你稍后将会看到一个这样的例子。

--exclude-module

||| 排除一些模块以防止其被包含进你的可执行文件中。

这对于排除只针对开发人员的需求很有用,比如测试框架。这是使你给用户的作品尽可能小的一个很棒的方法。例如,如果你使用pytest,那你可能想要将它从你的可执行文件中排除。

-w

||| 避免为stdout日志记录自动打开一个控制台窗口。

这只有在你构建支持GUI的应用程序时才有用。这使得用户永远看不到终端,从而帮助你隐藏你的实现的细节。

类似于--onefile选项,-w也不接受参数:

.spec文件

如前所述,你可以重用自动生成的.spec文件去进一步自定义你的可执行文件。此.spec文件是一个隐式使用PyInstaller库API的常规Python脚本。

因为它是一个常规的Python脚本,所以你几乎可以在其中做任何事情。有关该API的更多信息,你可以参考官方的PyInstaller Spec文件文档。

测试你的新可执行文件

测试你的新可执行文件的最佳方法是在一台新机器上。此新机器应该具有与你的构建机器相同的操作系统。理想情况下,这台机器应该尽可能与你的用户使用的机器相似。这也许并不总是可能的,所以下一个最好的方法是在你自己的机器上进行测试。

关键是在不激活开发环境的情况下运行生成的可执行文件。这意味着在没有virtualenv、conda或其它任何可以访问你的Python安装的环境下运行。请记住,一个Pyinstaller创建的可执行文件的主要目标之一是让用户不需要在他们的机器上安装任何东西。

继续以阅读器为例,你将注意到在dist/cli文件夹中运行默认的cli可执行文件失败了。幸运的是,这个错误为你指出了问题所在:

importlib_resources包需要一个version.txt文件。你可以使用--add-data选项将此文件添加到构建中。下面是一个如何将所需的version.txt文件包括进来的示例:

这条命令告诉PyInstaller将importlib_resources文件夹中的version.txt文件包括到你的构建中名为importlib_resources的新文件夹中。

注意: pyinstaller命令使用\字符来使命令更容易阅读。如下所示,如果你使用的是相同的路径,当你自己运行或复制粘贴此命令时,你可以忽略这个\。

你将需要调整上面命令中的路径,以匹配你安装阅读器依赖项的路径。

现在运行新的可执行文件将导致一个关于config.cfg文件的新错误。

此阅读器项目需要这个文件,所以你需要确保在你的构建中把它包括进来:

同样,你需要根据你存放该阅读器项目的位置调整该文件的路径。

此时,你应该有一个可以直接提供给用户的可执行文件!

调试PyInstaller可执行文件

如你在上面所见,你在运行你的可执行文件时可能会遇到问题。根据你项目的复杂性,修复可以非常简单,就像此feed reader例子一样你只需要将数据文件包括进来即可。然而,有时你需要更多的调试技术。

下面是一些常见的策略,它们没有特定的顺序。通常,这些策略中的一种或多种策略的组合将会在艰难的调试会话中为你带来突破。

使用终端

首先,尝试从终端运行该可执行文件,这样你就可以看到所有的输出。

记得删除-w 构建标志以查看控制台窗口中的所有stdout。通常,如果缺少某个依赖项,你将看到ImportError异常。

调试文件

检查build/cli/warn-cli.txt文件来找出任何问题。PyInstaller会创建大量输出,以帮助你准确理解它所创建的内容。在build/文件夹中进行搜索是一个很好的开始。

单目录构建

使用创建分发文件夹的--onedir分发模式代替单个可执行文件。同样,这是默认模式。使用--onedir构建可以让你有机会检查所有包括的依赖项,而不是隐藏在单个可执行文件中的所有内容。

--onedir对于调试很有用,但是--onefile通常更容易让用户理解。调试之后,你可能想切换到 --onefile模式以简化分发。

额外的CLI选项

PyInstaller还有控制构建过程中打印的信息量的选项。使用PyInstaller的--log-level=DEBUG选项重新构建该可执行文件,并检查输出。

当你使用--log-level=DEBUG选项增加冗长性时,PyInstaller将会创建大量输出。将此输出保存到一个你稍后可以引用的文件中,而不是在终端中滚动,这是非常有用的。为此,可以使用你shell的重定向功能。这里有一个例子:

通过使用上面的命令,您将得到一个名为build.txt的文件,其中包含了许多额外的DEBUG消息。

注意: 带有>的标准重定向是不够的。PyInstaller是打印到stderrstream,而不是stdout。这意味着您需要将stderr流重定向到一个文件,这可以像前面的命令一样使用一个2来完成。

下面是一个你的build.txt看起来是怎样的一个例子:

这个文件将包含许多关于构建中包括了什么、为什么有些东西没有被包括以及可执行文件是如何打包的详细信息。

除了使用--log-level选项获取更多信息外,你还可以使用--debug选项来重新构建你的可执行文件。

注意: -y 和 --clean选项在重新构建时非常有用,特别是在最初配置构建或使用持续集成进行构建时。这些选项会删除旧的构建,并省略了在构建过程中对用户输入的需要。

额外的PyInstaller文档

PyInstaller GitHub Wiki有很多有用的链接和调试提示。尤其是有关确保所有东西被正确打包和如果发生错误如何处理的章节。

依赖关系检测协助

如果PyInstaller不能正确地检测你的所有依赖项,你将看到的最常见问题是ImportError异常。如前所述,如果你使用的是 __import__()、函数内部的导入或其它类型的隐藏导入,那么就会发生这种情况。

许多这类问题都可以通过使用 --hidden-import PyInstaller CLI选项来解决。这会告诉PyInstaller包含一个模块或包,即使它没有自动检测到它。这是解决在你的应用程序中大量动态导入问题的最简单方法。

另一种解决问题的方法是钩子文件。这些文件包含附加信息,以帮助PyInstaller打包一个依赖项。你可以编写自己的钩子,并告诉PyInstaller通过--additional-hooks-dir CLI选项使用它们。

钩子文件是PyInstaller本身在内部工作的方式,因此,你可以在PyInstaller源代码中找到许多示例钩子文件。

限制

PyInstaller非常强大,但也有一些限制。我们前面讨论了一些限制:入口点脚本中的隐藏导入和相对导入。

PyInstaller支持为Windows、Linux和macOS生成可执行程序,但不能交叉编译。因此,您不能从另一个操作系统生成针对一个操作系统的可执行文件。因此,要为多种类型的操作系统分发可执行文件,您需要一个针对每一个支持的操作系统的构建机器。

与交叉编译限制相关的是,PyInstaller在技术上并不打包应用程序运行所需的所有内容,知道这一点很有用。你的可执行文件仍然依赖于用户的glibc。通常,你可以通过构建每个目标操作系统的最老版本来绕过glibc限制。

例如,如果你想要针对大量的Linux机器,那么你可以在一个较老版本的CentOS上进行构建。这将使你能够兼容比您构建的版本更新的大多数版本。这与PEP 0513中描述的策略相同,也是PyPA建议的构建兼容的轮子的策略。

实际上,你可能希望研究如何为你的Linux构建环境使用PyPA上的多个linux docker镜像。你可以从基础镜像开始,然后安装PyInstaller和所有依赖项,并拥有一个支持大多数Linux变体的构建镜像。

结论

PyInstaller可以使复杂的安装文档变得不必要。相反,你的用户可以简单地运行您的可执行文件来快速启动。PyInstaller的工作流可以总结如下:

创建一个调用主函数的入口点脚本。

安装PyInstaller。

在你的入口点脚本上运行PyInstaller。

测试新的可执行文件。

将生成的dist/文件夹发送给用户。

你的用户根本不需要知道你使用的是什么版本的Python,或者你的应用程序使用的是Python !

英文原文:https://realpython.com/pyinstaller-python/

译者:一瞬

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20190831A0488900?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券