将Python应用程序及其环境打包到MS Windows上供其他用户使用,使其能够在任何机器上“随时运行”,这是一项棘手的任务。这篇博文描述了我个人的解决方案:我称之为Windows版Python Bundle 的东西,它类似于虚拟环境,但在机器之间是可移植的。
Python Bundle 某种程度上位于虚拟环境、常规Python安装和由PyInstaller或Py2exe等工具创建的独立可执行文件提供的价值和权衡的交集处。
创建这样的Bundle不需要新的工具。这只是一个松散且轻量级的约定,用于文件夹结构和一些包装脚本,您可以轻松地手动创建它们。或者在脚本或CI作业中自动化它们的创建。
让我们假设我们要以自包含和“随时运行”的方式将Python应用程序或环境打包并交付给我们的用户。
我们可能不知道我们的用户安装了哪个版本的Python,或者根本没有安装。我们绝对不想篡改他们可能已经存在的Python安装,其中包括不让他们请求安装额外的Python版本。换句话说:我们的软件包应该是我们的用户运行我们的应用程序或使用我们的Python环境所需的一切。
经过一番集思广益,我们可能会想到创建一个虚拟环境(python -m venv venv_dir),将所有内容安装到虚拟环境中,然后将虚拟环境文件夹压缩并分发给我们的用户。但是,我们意识到虚拟环境文件夹不容易重新定位到与其创建位置不同的路径。此外,我们的虚拟环境还依赖于用于创建它的基础Python安装(在该路径下使用完全相同的Python版本)。因此,我们需要告诉我们的用户将虚拟环境的副本放在哪里。并且他们必须在特定路径下安装特定版本的Python。这不是我们想要的。
我们可以将所有需求安装到常规Python安装中,然后压缩并分发其文件夹(例如,c:Program FilesPython 3.13.1),而不是虚拟环境。这大部分有效。Windows上的Python安装目录通常可以重新定位到不同的路径(在Unix上由于静态前缀路径而并非如此——但这是另一个主题)。
但是,有一个很大的缺陷:存在脚本可执行文件(.Scripts目录中的.exe文件,通常在包不仅仅是库而且还提供脚本作为入口点时由pip创建。这样一个可执行文件就是pip.exe本身)。这些脚本可执行文件依赖于硬编码在其“自身”中的Python安装路径。
PyInstaller或Py2exe之类的工具可以将Python应用程序及其所有依赖项捆绑到单个包中。用户无需安装Python解释器或任何模块即可运行打包的应用程序。
这完美地解决了我们的分发需求。但是,权衡是我们没有分发完整的Python环境,而是一个自定义的、简化的Bundle格式。这可能是打包应用程序的正确工具。但是,如果我们例如想要向我们的用户发送一个可以在IDE中使用并通过额外的pip安装等进行扩展的“入门套件”Python环境,则它并不适用。我们正在寻找更通用的解决方案。
我们将在Powershell中开始,为我们的Bundle创建一个文件夹:
<code>mkdir bundle cd bundle</code>
然后,我们将在
<code>curl.exe -L "https://www.nuget.org/api/v2/package/python/3.13.1" -o python3.zip Expand-Archive .\python3.zip -DestinationPath extracted_nuget move .\extracted_nuget\tools python3 rm -R extracted_nuget rm -R .\python3.zip</code>
现在我们的Bundle看起来像这样:
<code>bundle └───python3 ├───python3.exe ├───Lib/ ├───...</code>
让我们也添加一个Scripts目录:
<code>mkdir python3\Scripts</code>
目前我们还没有启用pip,所以现在让我们这样做。
<code>python3\python.exe -m ensurepip</code>
为了解决pip在Scripts目录中创建依赖于硬编码Python安装路径的.exe文件的问题,我们将为pip使用一个包装脚本,该脚本修补pip的行为。
让我们创建一个文件
<code>#!/usr/bin/python import sys import os if __name__ == "__main__": from pip._vendor.distlib.scripts import ScriptMaker ScriptMaker.executable = r"python.exe" from pip._internal.cli.main import main sys.exit(main())</code>
它是如何工作的?每当我们通过我们的包装脚本安装一个包(例如,python3python.exe pip_wrapperscriptspip.py
当然,这是有风险的,并且有影响。现在,我们的工作是确保当有人执行这样的“修补的”Scripts*.exe文件时,PATH变量中的python.exe是正确的。这就是为什么我们的Bundle需要由用户激活,类似于虚拟环境。我们稍后会讨论这一点。
(有关这种pip包装器想法的更多信息,请参见此处)
现在,如果我们还有一个pip.exe用于我们的pip包装器,那不是很好吗?这样,我们以后就可以只使用pip命令(而不必使用python pip.py)?让我们创建一个。当然,它也需要是可移植的,这就是为什么我们将以类似的方式创建它。
为此,让我们创建一个
<code>mkdir bundle cd bundle</code>
然后让我们使用python3python.exe启动一个Python shell(REPL)并执行以下代码来创建pip.exe。
<code>curl.exe -L "https://www.nuget.org/api/v2/package/python/3.13.1" -o python3.zip Expand-Archive .\python3.zip -DestinationPath extracted_nuget move .\extracted_nuget\tools python3 rm -R extracted_nuget rm -R .\python3.zip</code>
我们的文件夹结构现在应该如下所示:
<code>bundle └───python3 ├───python3.exe ├───Lib/ ├───...</code>
**Bundle** | **虚拟环境** | **Python安装** | **Pyinstaller** | |
**路径独立(可以复制到文件系统中的任何路径)?** | 是 | 否 (Python安装路径硬编码在虚拟环境中) | 否 (.\scripts\*.exe文件将中断) | 是 |
**可以在同一系统上有多个实例** | 是 | 是 | 没有问题 (概念是一个Python版本每个用户或系统一个Python安装) | 是 |
**磁盘使用情况** | 大 (包含完整的Python安装) | 小 (依赖于Python安装) | 大 | 中等 |
**需要激活** | 是 | 是 | 否 | 否 |
**单个可执行文件** | 否 | 否 | 否 | 是 |
**可以用作常规Python安装(REPL、pip、脚本等)** | 是 | 是 | 是 | 否 |
**可以与IDE一起使用?** | 是,但您可能需要在IDE的运行/调试配置文件中配置环境变量 | 是 | 是 | 否 |
请注意,此输出对原文进行了改写,但保留了原文的全部信息和图片。 我使用了更流畅的表达方式,并对一些段落进行了重新组织,使其更易于理解。 图片的格式保持不变。
以上是窗户上的便携式Python捆的详细内容。更多信息请关注PHP中文网其他相关文章!