zipapp --- 実行可能な Python zip 書庫を管理する

バージョン 3.5 で追加.

ソースコード: Lib/zipapp.py


このモジュールは Python コードを含む zip ファイルの作成を行うツールを提供します。 zip ファイルは Python インタープリタで直接実行することが出来ます。 このモジュールは コマンドラインインターフェイスPython API の両方を提供します。

基本的な例

実行可能なアーカイブを Python コードを含むディレクトリから作成する為に コマンドラインインターフェイス をどのように利用することができるかを以下に例示します。アーカイブは実行時にアーカイブ内の myapp モジュールから main 関数を実行します。

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

コマンドラインインターフェイス

コマンドラインからプログラムとして呼び出す場合は、次の形式を使用います:

$ python -m zipapp source [options]

source がディレクトリである場合、 source ディレクトリの内容からアーカイブを作成します。 source がファイルである場合、 source ファイル自身をアーカイブ化し、保存先アーカイブへコピーします。(または --info オプションが指定されている場合はファイルのシェバン行が表示されます。)

以下のオプションが解釈されます:

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

出力を output に指定した名前のファイルへ書込みます。このオプションが指定されていない場合、出力先ファイル名は入力元 source と同じになり、 .pyz 拡張子が付与されます。 ファイル名が明示的に指定されている場合は、指定されたファイル名を使用します。(必要であれば .pyz 拡張子が含まれます。)

source がアーカイブである場合は、出力先ファイル名を必ず指定しなければなりません。 (source がアーカイブである場合は output を必ず source とは別の名前にしてください。)

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

実行コマンドとしての interpreter を指定する #! 行を書庫に追加します。 また、POSIX では書庫を実行可能にします。 デフォルトでは #! 行を書かず、ファイルを実行可能にはしません。

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

mainfn を実行するアーカイブへ __main__.py ファイルを書込んでください。 mainfn 引数は "pkg.mod:fn" の形式で指定します。 "pkg.mod" の場所はアーカイブ内の package/module です。 "fn" は指定した module から呼出すことのできる関数です。 __main__.py ファイルが module から呼出すことのできる関数を実行します。

書庫をコピーする際、 --main を指定することは出来ません。

-c, --compress

Compress files with the deflate method, reducing the size of the output file. By default, files are stored uncompressed in the archive.

書庫をコピーする際、 --compress に効果はありません。

バージョン 3.7 で追加.

--info

診断するために書庫に埋め込まれたインタープリタを表示します。 この場合、他の全てのオプションは無視され、SOURCE はディレクトリではなく書庫でなければなりません。

-h, --help

簡単な使用法を表示して終了します。

Python API

このモジュールは 2 つの簡便関数を定義しています:

zipapp.create_archive(source, target=None, interpreter=None, main=None, filter=None, compressed=False)

source からアプリケーション書庫を作成します。 ソースは以下のいずれかです:

  • ディレクトリ名、または 新しいアプリケーションアーカイブがディレクトリのコンテンツから作成される場合に path-like object オブジェクトが参照するディレクトリ。

  • 既存のアプリケーションアーカイブファイルの名前、 または(interpreter 引数に指定した値を反映し、修正する)アーカイブへ ファイルがコピーされる場合に path-like object オブジェクトが参照するファイル。

  • バイトモードの読込みで開くファイルオブジェクト。 ファイルの内容がアプリケーションアーカイブとなり、 ファイルオブジェクトがアーカイブの起点となります。

target 引数は作成される書庫が書き込まれる場所を決めます:

  • ファイル名、または path-like object オブジェクトを指定した場合、アーカイブは指定したファイルへ書き込まれます。

  • 開いているファイルオブジェクトを指定した場合、 アーカイブはそのファイルオブジェクトへ書込みを行ないます。 ファイルオブジェクトは必ずバイトモードの書込みで開いてください。

  • target を指定しないか None を渡した場合、 source は必ずディレクトリでなければならず、target は source のファイル名に .pyz 拡張子を付与したファイル名となります。

interpreter 引数はアーカイブが実行時に使用する Python インタープリタの名前を指定します。 インタープリタ名は "シェバン" 行としてアーカイブの起点に書込まれます。 POSIX では OS によってシェバンが解釈され、 Windows では Python ランチャーによって扱われます。 シェバン行が書込まれていない場合は interpreter の結果を無視します。 interpreter が指定されており、 target がファイル名である場合、 target ファイルの実行可能ビットが設定されます。

main 引数はアーカイブのメインプログラムとして使用する callable の名前を指定します。 main 引数は source がディレクトリであり、 source が既に __main__.py ファイルを保持していない場合に限り、指定することができます。 main 引数は "pkg.module:callable" の形式を取り、 アーカイブは "pkg.module" をインポートして実行され、 指定した callable を引数なしで実行します。 source がディレクトリであり、 __main__.py が含まれていない場合、 main は無視すべきエラーとなり、 作成されたアーカイブには実行可能ビットが設定されません。

The optional filter argument specifies a callback function that is passed a Path object representing the path to the file being added (relative to the source directory). It should return True if the file is to be added.

The optional compressed argument determines whether files are compressed. If set to True, files in the archive are compressed with the deflate method; otherwise, files are stored uncompressed. This argument has no effect when copying an existing archive.

source または target へファイルオブジェクトを指定した場合、 caller が create_archive の呼出し後にオブジェクトを閉じます。

既存のアーカイブをコピーする際、ファイルオブジェクトは read , readline , write メソッドのみを提供します。 アーカイブをディレクトリから作成する際、 target がファイルオブジェクトである場合は、 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', 'myapp.pyz')

POSIX でアプリケーションを直接実行可能にするには使用するインタープリタを指定します。

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

シバン行を既存の書庫で置換するには、 create_archive() function: を使用して変更された書庫を作成します:

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

To update the file in place, do the replacement in memory using a BytesIO object, and then overwrite the source afterwards. Note that there is a risk when overwriting a file in place that an error will result in the loss of the original file. This code does not protect against such errors, but production code should do so. Also, this method will only work if the archive fits in memory:

>>> 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())

インタープリタの指定

Note that if you specify an interpreter and then distribute your application archive, you need to ensure that the interpreter used is portable. The Python launcher for Windows supports most common forms of POSIX #! line, but there are other issues to consider:

  • If you use "/usr/bin/env python" (or other forms of the "python" command, such as "/usr/bin/python"), you need to consider that your users may have either Python 2 or Python 3 as their default, and write your code to work under both versions.

  • If you use an explicit version, for example "/usr/bin/env python3" your application will not work for users who do not have that version. (This may be what you want if you have not made your code Python 2 compatible).

  • There is no way to say "python X.Y or later", so be careful of using an exact version like "/usr/bin/env python3.4" as you will need to change your shebang line for users of Python 3.5, for example.

Typically, you should use an "/usr/bin/env python2" or "/usr/bin/env python3", depending on whether your code is written for Python 2 or 3.

Creating Standalone Applications with zipapp

Using the zipapp module, it is possible to create self-contained Python programs, which can be distributed to end users who only need to have a suitable version of Python installed on their system. The key to doing this is to bundle all of the application's dependencies into the archive, along with the application code.

The steps to create a standalone archive are as follows:

  1. Create your application in a directory as normal, so you have a myapp directory containing a __main__.py file, and any supporting application code.

  2. Install all of your application's dependencies into the myapp directory, using pip:

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

    (this assumes you have your project requirements in a requirements.txt file - if not, you can just list the dependencies manually on the pip command line).

  3. Optionally, delete the .dist-info directories created by pip in the myapp directory. These hold metadata for pip to manage the packages, and as you won't be making any further use of pip they aren't required - although it won't do any harm if you leave them.

  4. Package the application using:

    $ python -m zipapp -p "interpreter" myapp
    

This will produce a standalone executable, which can be run on any machine with the appropriate interpreter available. See インタープリタの指定 for details. It can be shipped to users as a single file.

On Unix, the myapp.pyz file is executable as it stands. You can rename the file to remove the .pyz extension if you prefer a "plain" command name. On Windows, the myapp.pyz[w] file is executable by virtue of the fact that the Python interpreter registers the .pyz and .pyzw file extensions when installed.

Making a Windows executable

On Windows, registration of the .pyz extension is optional, and furthermore, there are certain places that don't recognise registered extensions "transparently" (the simplest example is that subprocess.run(['myapp']) won't find your application - you need to explicitly specify the extension).

On Windows, therefore, it is often preferable to create an executable from the zipapp. This is relatively easy, although it does require a C compiler. The basic approach relies on the fact that zipfiles can have arbitrary data prepended, and Windows exe files can have arbitrary data appended. So by creating a suitable launcher and tacking the .pyz file onto the end of it, you end up with a single-file executable that runs your application.

A suitable launcher can be as simple as the following:

#define Py_LIMITED_API 1
#include "Python.h"

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#ifdef WINDOWS
int 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 */
)
#else
int 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);
}

If you define the WINDOWS preprocessor symbol, this will generate a GUI executable, and without it, a console executable.

To compile the executable, you can either just use the standard MSVC command line tools, or you can take advantage of the fact that distutils knows how to compile Python source:

>>> 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")

The resulting launcher uses the "Limited ABI", so it will run unchanged with any version of Python 3.x. All it needs is for Python (python3.dll) to be on the user's PATH.

For a fully standalone distribution, you can distribute the launcher with your application appended, bundled with the Python "embedded" distribution. This will run on any PC with the appropriate architecture (32 bit or 64 bit).

Caveats

There are some limitations to the process of bundling your application into a single file. In most, if not all, cases they can be addressed without needing major changes to your application.

  1. If your application depends on a package that includes a C extension, that package cannot be run from a zip file (this is an OS limitation, as executable code must be present in the filesystem for the OS loader to load it). In this case, you can exclude that dependency from the zipfile, and either require your users to have it installed, or ship it alongside your zipfile and add code to your __main__.py to include the directory containing the unzipped module in sys.path. In this case, you will need to make sure to ship appropriate binaries for your target architecture(s) (and potentially pick the correct version to add to sys.path at runtime, based on the user's machine).

  2. If you are shipping a Windows executable as described above, you either need to ensure that your users have python3.dll on their PATH (which is not the default behaviour of the installer) or you should bundle your application with the embedded distribution.

  3. The suggested launcher above uses the Python embedding API. This means that in your application, sys.executable will be your application, and not a conventional Python interpreter. Your code and its dependencies need to be prepared for this possibility. For example, if your application uses the multiprocessing module, it will need to call multiprocessing.set_executable() to let the module know where to find the standard Python interpreter.

The Python Zip Application Archive Format

Python has been able to execute zip files which contain a __main__.py file since version 2.6. In order to be executed by Python, an application archive simply has to be a standard zip file containing a __main__.py file which will be run as the entry point for the application. As usual for any Python script, the parent of the script (in this case the zip file) will be placed on sys.path and thus further modules can be imported from the zip file.

The zip file format allows arbitrary data to be prepended to a zip file. The zip application format uses this ability to prepend a standard POSIX "shebang" line to the file (#!/path/to/interpreter).

Formally, the Python zip application format is therefore:

  1. An optional shebang line, containing the characters b'#!' followed by an interpreter name, and then a newline (b'\n') character. The interpreter name can be anything acceptable to the OS "shebang" processing, or the Python launcher on Windows. The interpreter should be encoded in UTF-8 on Windows, and in sys.getfilesystemencoding() on POSIX.

  2. Standard zipfile data, as generated by the zipfile module. The zipfile content must include a file called __main__.py (which must be in the "root" of the zipfile - i.e., it cannot be in a subdirectory). The zipfile data can be compressed or uncompressed.

If an application archive has a shebang line, it may have the executable bit set on POSIX systems, to allow it to be executed directly.

There is no requirement that the tools in this module are used to create application archives - the module is a convenience, but archives in the above format created by any means are acceptable to Python.