Документация Python неофициальный перевод
Содержание страницы

28.4. zipapp – Управление исполняемыми python zip-архивамиzipapp – Manage executable python zip archives

Новое в версии 3.5.

Исходный код: Lib/zipapp.py


This module provides tools to manage the creation of zip files containing Python code, which can be executed directly by the Python interpreter. The module provides both a Command-Line Interface and a Python API.

28.4.1. Простой примерBasic Example

В следующем примере показано, как с помощью интерфейса командной строки можно создать исполняемый архив из каталога с кодом Python. При запуске архив выполнит функцию main из модуля myapp.

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

28.4.2. Интерфейс командной строкиCommand-Line Interface

При вызове как программы из командной строки используется следующая форма:

$ python -m zipapp source [options]

If source is a directory, this will create an archive from the contents of source. If source is a file, it should be an archive, and it will be copied to the target archive (or the contents of its shebang line will be displayed if the –info option is specified).

Поддерживаются следующие опции:

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

Записывает результат в файл с именем output. Если эта опция не указана, имя выходного файла будет таким же, как у входного source, но с добавлением расширения .pyz. Если задано явное имя файла, оно используется как есть (поэтому при необходимости следует указать расширение .pyz).

Имя выходного файла должно быть указано, если source является архивом (в этом случае output не должно совпадать с source).

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

Добавляет в архив строку #!, указывающую interpreter в качестве команды для запуска. Также на POSIX делает архив исполняемым. По умолчанию строка #! не записывается, и файл не делается исполняемым.

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

Записывает в архив файл __main__.py, который выполняет mainfn. Аргумент mainfn должен иметь вид «pkg.mod:fn», где «pkg.mod» – пакет или модуль в архиве, а «fn» – вызываемый объект в этом модуле. Файл __main__.py выполнит этот вызываемый объект.

--main нельзя указывать при копировании архива.

--info

Выводит интерпретатор, встроенный в архив, для диагностики. В этом случае все остальные опции игнорируются, а SOURCE должен быть архивом, а не каталогом.

-h, --help

Выводит краткую справку и завершает работу.

28.4.3. Python API

Модуль определяет две вспомогательные функции:

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

Создаёт архив приложения из source. Источником может быть любой из перечисленных вариантов:

  • Имя каталога или объект pathlib.Path, ссылающийся на каталог; в этом случае из содержимого этого каталога будет создан новый архив приложения.

  • Имя существующего файла архива приложения или объект pathlib.Path, ссылающийся на такой файл; в этом случае файл копируется в целевой объект (с изменением для отражения значения, указанного для аргумента interpreter). Имя файла должно включать расширение .pyz, если это необходимо.

  • Файловый объект, открытый для чтения в байтовом режиме. Содержимое файла должно быть архивом приложения, и предполагается, что файловый объект находится в начале архива.

Аргумент target определяет, куда будет записан результирующий архив:

  • Если это имя файла или объект pathlb.Path, архив будет записан в этот файл.

  • Если это открытый файловый объект, архив будет записан в этот файловый объект, который должен быть открыт для записи в байтовом режиме.

  • Если цель опущена (или None), источник должен быть каталогом, и целью будет файл с тем же именем, что и источник, с\добавленным расширением .pyz.

Аргумент interpreter задаёт имя интерпретатора Python, с которым будет выполняться архив. Он записывается в виде строки «shebang» в начале архива. На POSIX эта строка будет интерпретироваться ОС, а на Windows – обрабатываться загрузчиком Python. Если опустить interpreter, строка shebang не записывается. Если интерпретатор указан, а целью является имя файла, то устанавливается бит исполняемости целевого файла.

Аргумент main задаёт имя вызываемого объекта, который будет использоваться как главная программа архива. Его можно указать только в том случае, если источник – каталог и в нём ещё нет файла __main__.py. Аргумент main должен иметь форму «pkg.module:callable», и архив будет выполняться путём импорта «pkg.module» и вызова указанного вызываемого объекта без аргументов. Будет ошибкой опустить main, если источник – каталог и не содержит файла __main__.py, так как иначе полученный архив не будет исполняемым.

Если для source или target указан файловый объект, вызывающая сторона обязана закрыть его после вызова create_archive.

При копировании существующего архива предоставленные файловые объекты должны иметь только методы read и readline или write. При создании архива из каталога, если цель – файловый объект, он будет передан классу zipfile.ZipFile и должен предоставлять методы, необходимые этому классу.

zipapp.get_interpreter(archive)

Возвращает интерпретатор, указанный в строке #! в начале архива. Если строка #! отсутствует, возвращает None. Аргумент archive может быть именем файла или файлоподобным объектом, открытым для чтения в байтовом режиме. Предполагается, что он находится в начале архива.

28.4.4. ПримерыExamples

Упаковывает каталог в архив и запускает его.

$ 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, а затем перезапишите исходный файл. Обратите внимание, что при перезаписи файла на месте существует риск, что ошибка приведёт к потере исходного файла. Данный код не защищает от таких ошибок, но рабочий код должен это делать. Кроме того, этот метод будет работать только в том случае, если архив помещается в память:

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

28.4.5. Указание интерпретатораSpecifying the Interpreter

Обратите внимание: если вы указываете интерпретатор и затем распространяете архив своей программы, необходимо убедиться, что используемый интерпретатор переносим. Загрузчик Python для Windows поддерживает большинство распространённых форм строки POSIX #!, но есть другие моменты, которые стоит учесть:

  • Если вы используете «/usr/bin/env python» (или другие формы команды «python», например «/usr/bin/python»), нужно учитывать, что у ваших пользователей может быть установлен по умолчанию как Python 2, так и Python 3, и писать свой код так, чтобы он работал в обеих версиях.

  • Если вы используете явную версию, например «/usr/bin/env python3», ваше приложение не будет работать у пользователей, у которых нет этой версии. (Это может быть тем, что вам нужно, если вы не делали свой код совместимым с Python 2).

  • Невозможно указать «python X.Y или новее», поэтому будьте осторожны с использованием точной версии, например «/usr/bin/env python3.4», так как вам придётся изменить строку shebang для пользователей Python 3.5, например.

Обычно стоит использовать «/usr/bin/env python2» или «/usr/bin/env python3», в зависимости от того, для Python 2 или 3 написан ваш код.

28.4.6. Создание автономных приложений с помощью zipappCreating Standalone Applications with 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-info, созданные pip в каталоге myapp. Они содержат метаданные, необходимые pip для управления пакетами, и поскольку pip больше не будет использоваться, они не обязательны – хотя вреда от их присутствия не будет.

  4. Упакуйте приложение с помощью:

    $ python -m zipapp -p "interpreter" myapp
    

Это создаст автономный исполняемый файл, который можно запустить на любой машине с подходящим интерпретатором. Подробнее см. Указание интерпретатора. Его можно распространять среди пользователей как один файл.

В Unix файл myapp.pyz является исполняемым как есть. Вы можете переименовать файл, чтобы удалить расширение .pyz, если предпочитаете «простое» имя команды. В Windows файл myapp.pyz[w] является исполняемым благодаря тому, что интерпретатор Python регистрирует расширения файлов .pyz и .pyzw при установке.

28.4.6.1. Создание исполняемого файла WindowsMaking a Windows executable

В Windows регистрация расширения .pyz необязательна; кроме того, есть места, которые «прозрачно» не распознают зарегистрированные расширения (самый простой пример: subprocess.run(['myapp']) не найдет ваше приложение – нужно явно указать расширение).

Поэтому в Windows часто предпочтительнее создать исполняемый файл из zipapp. Это относительно просто, хотя и требует компилятора C. Основной подход основан на том, что в zip-файлы можно добавлять произвольные данные в начало, а в Windows exe-файлы – в конец. Создав подходящий загрузчик и прикрепив файл .pyz к его концу, вы получите однофайловый исполняемый файл, запускающий ваше приложение.

Подходящий загрузчик может быть таким же простым, как следующий:

#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);
}

Если определить символ препроцессора WINDOWS, будет создан исполняемый файл с графическим интерфейсом, а без него – консольный.

Для компиляции исполняемого файла можно либо использовать стандартные средства командной строки MSVC, либо воспользоваться тем, что distutils умеет компилировать исходники 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'))
>>>     # Сначала CLI-исполняемый файл
>>>     objs = cc.compile([str(src)])
>>>     cc.link_executable(objs, exe)
>>>     # Теперь GUI-исполняемый файл
>>>     cc.define_macro('WINDOWS')
>>>     objs = cc.compile([str(src)])
>>>     cc.link_executable(objs, exe + 'w')

>>> if __name__ == "__main__":
>>>     compile("zastub.c")

Полученный загрузчик использует «Limited ABI», поэтому он будет работать без изменений с любой версией Python 3.x. Всё, что ему нужно – чтобы Python (python3.dll) находился в PATH пользователя.

Для полностью автономного распространения можно распространять загрузчик с вашим приложением, прикреплённым к нему, в комплекте с «встраиваемым» дистрибутивом Python. Он будет работать на любом ПК с соответствующей архитектурой (32 или 64 бита).

28.4.6.2. ПредостереженияCaveats

У процесса упаковки приложения в один файл есть некоторые ограничения. В большинстве случаев (если не во всех) их можно обойти без серьёзных изменений в приложении.

  1. Если ваше приложение зависит от пакета, содержащего C-расширение, этот пакет нельзя запускать из zip-файла (это ограничение ОС: исполняемый код должен находиться в файловой системе, чтобы загрузчик ОС мог его загрузить). В таком случае можно исключить эту зависимость из zip-файла и либо потребовать от пользователей её установки, либо поставлять её вместе с zip-файлом и добавить код в __main__.py, чтобы включить каталог, содержащий распакованный модуль, в sys.path. В этом случае нужно убедиться, что поставляются подходящие бинарные файлы для целевой архитектуры (и, возможно, выбирать правильную версию для добавления в sys.path во время выполнения в зависимости от машины пользователя).

  2. Если вы распространяете исполняемый файл для Windows, как описано выше, вам нужно либо убедиться, что у пользователей есть python3.dll в PATH (это не поведение установщика по умолчанию), либо упаковать приложение вместе со встраиваемым дистрибутивом.

  3. Предложенный выше загрузчик использует встраиваемый API Python. Это означает, что в вашем приложении sys.executable будет вашим приложением, а не обычным интерпретатором Python. Ваш код и его зависимости должны быть готовы к этому. Например, если ваше приложение использует модуль multiprocessing, ему нужно будет вызвать multiprocessing.set_executable(), чтобы сообщить модулю, где найти стандартный интерпретатор Python.

28.4.7. Формат архива приложений Python ZipThe Python Zip Application Archive Format

Python умеет выполнять zip-файлы, содержащие файл __main__.py, начиная с версии 2.6. Чтобы Python мог выполнить архив приложения, он должен быть обычным zip-файлом, содержащим файл __main__.py, который будет запущен как точка входа приложения. Как и для любого скрипта Python, родительский каталог скрипта (в данном случае zip-файл) будет добавлен в sys.path, и, таким образом, другие модули можно будет импортировать из zip-файла.

Формат zip-файлов позволяет добавлять произвольные данные в начало zip-файла. Формат архива приложений использует эту возможность для добавления стандартной POSIX-строки «шебанг» в начало файла (#!/path/to/interpreter).

Формально формат zip-архива приложений Python определяется так:

  1. Необязательная строка шебанга, содержащая символы b'#!', за которыми следует имя интерпретатора, а затем символ новой строки (b'\n'). Имя интерпретатора может быть любым, приемлемым для обработки шебанга в ОС или для загрузчика Python в Windows. На Windows интерпретатор должен быть закодирован в UTF-8, а на POSIX – в sys.getfilesystemencoding().

  2. Стандартные данные zip-файла, создаваемые модулем zipfile. Содержимое zip-файла должно включать файл с именем __main__.py (который должен находиться в «корне» zip-файла, то есть не в подкаталоге). Данные zip-файла могут быть сжатыми или несжатыми.

Если архив приложения содержит строку шебанга, на POSIX-системах у него может быть установлен бит исполняемости, чтобы его можно было запускать напрямую.

Не требуется, чтобы архивы приложений создавались с помощью инструментов из этого модуля – модуль предоставляет удобные средства, но архивы указанного формата, созданные любым способом, принимаются Python.