zipapp– 管理可执行的Python zip档案

3.5版本新增.

源代码: Lib / zipapp.py


这个模块提供了管理包含Python代码的zip文件创建的工具,可以是由Python解释器直接执行。该模块提供了命令行界面 Python API .

基本示例

以下示例显示了命令行界面可用于从包含Python代码的目录创建可执行归档。运行时,存档将从存档中的main执行myapp功能.

$ python -m zipapp myapp -m "myapp:main"$ python myapp.pyz<output from myapp>

命令行界面

当从命令行调用程序时,使用以下形式:

$ python -m zipapp source [options]

如果source是一个目录,这将创建一个来自source的内容。如果source是一个文件,它应该是一个存档,它将被发送到目标存档(或者如果指定了-info选项,它将显示其shebang行的内容).

可以理解以下选项:

-o <output>, --output=<output>

将输出写入名为output的文件。如果未指定此选项,则输出文件名将与输入source相同,并添加了扩展.pyz。如果给出了显式文件名,则使用asis(如果需要,应该包括.pyz扩展名).

如果source是一个输出文件名必须指定存档(并且在这种情况下,output必须与source)相同.

-p <interpreter>, --python=<interpreter>

在存档中添加#!行指定interpreter作为命令运行。此外,在POSIX上,使存档可执行。默认为towrite no #! line,而不是使文件可执行.

-m <mainfn>, --main=<mainfn>

__main__.py文件写入执行mainfn的存档。mainfn参数的格式应为“pkg.mod:fn”,其中“pkg.mod”是存档中的apackage / module,“fn”是给定模块中的可调用的.__main__.py文件将执行该callable.

--main在复制存档时无法指定.

-c, --compress

使用deflate方法压缩文件,减小输出文件的大小。默认情况下,文件以未压缩的形式存储在存档中.

--compress在复制存档时无效.

新版本3.7.

--info

显示存档中嵌入的解释器,用于诊断目的。在这种情况下,任何其他选项都被忽略,SOURCE必须是存档,而不是adirectory.

-h, --help

打印一条简短的用法消息并退出.

Python API

模块定义了两个便利功能:

zipapp.create_archivesource, target=None, interpreter=None, main=None, filter=None, compressed=False

source创建应用程序存档。源可以是以下任何一种:

  • 目录的名称,或路径类对象引用目录,在这种情况下,新的应用程序归档将从该目录的内容中取出.
  • 现有应用程序存档文件的名称,或者路径类对象引用这样的文件,在这种情况下文件被复制到目标(修改它以反映给出的值interpreter参数)。如果需要,文件名应包含.pyz扩展名.
  • 打开文件对象以便以字节模式读取。文件的内容应该是一个应用程序存档,并且文件对象被认为是位于存档的开头.

target参数确定生成的存档的写入位置:

  • 如果是文件的名称,或者类似路径的对象,则存档将被写入文件。
  • 如果它是一个打开的文件对象,则存档将被写入该文件对象,该对象必须打开以便以字节模式写入.
  • 如果省略目标(或None),源必须是一个目录,目标将是一个与源名称相同的文件,添加.pyz扩展名

// interpreterargument指定将用于执行存档的Pythoninterpreter的名称。它在归档的开头写成了“shebang”行。在POSIX上,这将由操作系统解释,在Windows上,它将由Pythonlauncher处理。省略interpreter导致没有shebang行被写入。如果指定了解释器,并且目标是afilename,则将设置目标文件的可执行位.

mainargument指定将用作归档主程序的可调用对象的名称。它只能在源是目录时指定,并且源不包含__main__.py文件。main参数应采用“pkg.module:callable”形式,并通过导入“pkg.module”并执行给定的callable而不运行参数来运行存档。省略main如果源是一个目录而且不包含__main__.py文件,否则生成的存档将不可执行.

可选filterargument指定一个回调函数,该函数传递一个Path对象,该对象表示要添加的文件的路径(相对于源目录)。它应该返回True如果要添加文件

可选的compressed参数确定文件是否压缩。如果设置为True,档案中的文件用deflate方法压缩;否则,文件以未压缩的方式存储。复制现有存档时此参数无效.

如果为sourcetarget指定了文件对象,则调用create_archive后,调用者有责任将其关闭.

复制现有存档时,提供的文件对象只需要readreadline,或write方法。从目录创建anarchive时,如果目标是文件对象,它将被传递到zipfile.ZipFile类,并且必须提供该类所需的方法.

版本3.7中的新功能:添加了filtercompressed参数.

zipapp.get_interpreter (archive)

返回在原始文件开头的#!行中指定的解释器。如果没有#!线,则返回None.archive参数可以是文件名或类似文件的对象,以字节模式读取。假设它是存档的开头.

例子

将一个目录打包到一个存档中,并运行它.

$ python -m zipapp myapp$ python myapp.pyz<output from myapp>

使用create_archive()功能:

>>> import zipapp>>> zipapp.create_archive("myapp.pyz", "myapp")

可以使应用程序直接在POSIX上执行,指定一个解释用途

$ python -m zipapp myapp -p "/usr/bin/env python"$ ./myapp.pyz<output from myapp>

要替换现有存档上的shebang行,使用create_archive()函数创建修改后的存档:

>>> import zipapp>>> zipapp.create_archive("old_archive.pyz", "new_archive.pyz", "/usr/bin/python3")

要更新文件,请使用BytesIO对象,然后覆盖源。请注意,在覆盖文件时存在风险,错误将导致丢失原始文件。此代码不能防止此类错误,但生产代码应该这样做。此外,此方法仅适用于内存中的archivefits:

>>> import zipapp>>> import io>>> temp = io.BytesIO()>>> zipapp.create_archive("myapp.pyz", temp, "/usr/bin/python2")>>> with open("myapp.pyz", "wb") as f:>>>     f.write(temp.getvalue())

指定口译员

请注意,如果指定解释器然后分发applicationarchive,则需要确保使用的解释器是可移植的。用于Windows的Pythonlauncher支持大多数常见形式的POSIX #! line,但还有其他需要考虑的问题:

  • 如果使用“/ usr / bin / env python”(或其他形式的“python“命令,例如”/ usr / bin / python“),您需要考虑您的用户可能将Python 2或Python 3作为默认值,并编写代码以在两个版本下工作.
  • 如果您使用显式版本,例如“/ usr / bin / env python3”,您的应用程序将不适用于没有该版本的用户。(这可能是你想要的,如果你没有使你的代码Python 2兼容).
  • 没有办法说“python XY或更晚”,所以要小心使用像“/ usr / bin”这样的非精确版本/ env python3.4“因为你需要为Python 3.5的用户更改yourshebang行,例如

通常,您应该使用“/ usr / bin / env python2”或“/ usr / bin / env python3”,具体取决于您的代码是为Python 2还是3编写的.

使用zipapp创建独立应用程序

使用zipapp模块,可以创建自包含的Python程序,可以分发给只需要在其系统上安装适当版本的Python的最终用户。这样做的关键是将所有应用程序的依赖项与应用程序代码一起捆绑到存档中.

创建独立存档的步骤如下:

  1. 正常在目录中创建应用程序,因此您有myapp目录包含__main__.py文件,以及任何支持的应用程序代码.

  2. 将所有应用程序的依赖项安装到myapp目录,使用pip:

    $ python -m pip install -r requirements.txt --target myapp

    (这假设你的项目要求在requirements.txt文件 – 如果没有,你可以在pip命令行上手动列出依赖项。)

  3. 可选,删除.dist-infopip在myapp目录中创建的目录。这些包含pip的元数据来管理包,并且你不会进一步使用pip它们不是必需的 – 尽管你离开它们不会造成任何伤害.

  4. 包装应用程序using:

    $ python -m zipapp -p "interpreter" myapp

这将生成一个独立的可执行文件,可以在任何具有适当解释器的机器上运行。有关详细信息,请参阅指定解释器。它可以作为单个文件发送给用户.

在Unix上,myapp.pyz文件是可执行的。如果您更喜欢“普通”命令名,可以重命名该文件以删除.pyz扩展名。在OnWindows上,myapp.pyz[w]文件是可执行的,因为Python解释器在安装时注册了.pyz.pyzw文件扩展名.

使用Windows可执行文件

Windows上,.pyz扩展是可选的,而且,某些地方无法“透明地”识别registeredextensions(最简单的例子是subprocess.run(["myapp"])找不到您的应用程序 – 您需要明确指定扩展名.

因此,在Windows上,通常最好从thezipapp创建可执行文件。这是相对容易的,虽然它确实需要C编译器。基本方法依赖于zipfiles可以具有任意数据重挂的事实,并且Windows exe文件可以附加任意数据。所以通过创建一个合适的发射器并抓住.pyz文件到它的末尾,你最终得到一个运行你的应用程序的单文件可执行文件.

一个合适的启动器可以简单如下:

#define Py_LIMITED_API 1#include "Python.h"#define WIN32_LEAN_AND_MEAN#include <windows.h>#ifdef WINDOWSint WINAPI wWinMain(    HINSTANCE hInstance,      /* handle to current instance */    HINSTANCE hPrevInstance,  /* handle to previous instance */    LPWSTR lpCmdLine,         /* pointer to command line */    int nCmdShow              /* show state of window */)#elseint wmain()#endif{    wchar_t **myargv = _alloca((__argc + 1) * sizeof(wchar_t*));    myargv[0] = __wargv[0];    memcpy(myargv + 1, __wargv, __argc * sizeof(wchar_t *));    return Py_Main(__argc+1, myargv);}

如果你定义WINDOWS预处理器符号,这将生成一个GUI可执行文件,没有它,一个控制台可执行文件.

要编译可执行文件,您可以只使用标准的MSVCcommand行工具,或者您可以利用distutilsknows如何编译Python源的事实:

>>> from distutils.ccompiler import new_compiler>>> import distutils.sysconfig>>> import sys>>> import os>>> from pathlib import Path>>> def compile(src):>>>     src = Path(src)>>>     cc = new_compiler()>>>     exe = src.stem>>>     cc.add_include_dir(distutils.sysconfig.get_python_inc())>>>     cc.add_library_dir(os.path.join(sys.base_exec_prefix, "libs"))>>>     # First the CLI executable>>>     objs = cc.compile([str(src)])>>>     cc.link_executable(objs, exe)>>>     # Now the GUI executable>>>     cc.define_macro("WINDOWS")>>>     objs = cc.compile([str(src)])>>>     cc.link_executable(objs, exe + "w")>>> if __name__ == "__main__":>>>     compile("zastub.c")

生成的启动程序使用“受限ABI”,因此它将运行不变的Python 3.x版本。它所需要的只是Python(python3.dll)用户的PATH.

对于完全独立的发行版,您可以使用附加的应用程序分发启动程序,与Python“嵌入式”发行版捆绑在一起。这将在具有适当架构(32位或64位)的任何PC上运行.

警告

将应用程序捆绑到单个文件的过程存在一些限制。在大多数(如果不是全部)情况下,可以在不对应用程序进行重大更改的情况下解决它们.

  1. 如果您的应用程序依赖于包含C扩展的包,则无法从zip文件运行该包(这是操作系统限制,因为必须在文件系统中存在执行代码才能加载OS加载程序)。在这种情况下,您可以从zipfile中排除该依赖项,并要求您的用户安装它,或者将它与您的zipfile一起发送并将代码添加到您的__main__.pysys.path。在这种情况下,您需要确保为目标体系结构提供适当的二进制文件(并可能选择正确的版本添加到sys.path在运行时,基于用户的机器).
  2. 如果您正在运送如上所述的Windows可执行文件,您需要确保您的用户有python3.dll在他们的PATH(这不是安装程序的默认行为)或您应该将您的应用程序与嵌入式发行版捆绑在一起
  3. 上面建议的启动器使用Python嵌入API。这意味着在你的应用程序中,sys.executable将是你的应用程序,而not是一个非传统的Python解释器。您的代码及其依赖关系需要为这种可能性做好准备。例如,如果您的应用程序使用multiprocessing模块,则需要调用multiprocessing.set_executable()让模块知道在哪里找到标准的Python解释器.

Python Zip应用程序存档格式

Python已经能够执行包含__main__.py filesince 2.6版的zip文件。为了由Python执行,应用程序归档必须是包含__main__.py文件的标准zip文件,该文件将作为应用程序的入口点运行。与任何Pythonscript一样,脚本的父级(在本例中为zip文件)将放在sys.path上,因此可以从zip文件中导入更多模块.

zip文件格式允许将任意数据添加到zip文件中。Thezip应用程序格式使用此功能将标准POSIX“shebang”行添加到文件中(#!/path/to/interpreter).

因此,Python zip应用程序格式为:

  1. An可选的shebang行,包含字符b"#!"后跟aninterpreter名称,然后是换行符(b"\n")。解释名称可以是OS“shebang”处理可接受的任何内容,也可以是Windows上的Pythonlauncher。解释器应该在Windows上以UTF-8编码,在sys.getfilesystemencoding()在POSIX上编码
  2. zipfile模块生成的标准zipfile数据。thezipfile内容must包含一个名为__main__.py的文件(必须位于zipfile的“根”中 – 即,它不能位于子目录中)。thezipfile数据可以压缩或解压缩.

如果一个应用程序存档有一个shebang行,它可能有可执行位seton POSIX系统,允许它直接执行.

没有要求使用此模块中的工具来创建应用程序存档 – 该模块是一种方便,但是以任何方式创建的上述格式的存档都是Python可接受的.