zipapp — Управление исполняемыми zip-архивами Python

Добавлено в версии 3.5.


Данный модуль предоставляет инструменты для управления созданием zip-файлов, содержащих Python код, который может выполняется непосредственно интерпретатором 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).

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

-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 нельзя указывать при копировании архива.

-c, --compress

Сжимает файлы методом deflate, уменьшая размер выходного файла. По умолчанию файлы хранятся в несжатом виде в архиве.

--compress не действует при копировании архива.

Добавлено в версии 3.7.

--info

Отображение интерпретатора, встроенного в архив, для диагностических целей. В этом случае любые другие параметры игнорируются, а ИСТОЧНИК должен быть архивом, а не каталогом.

-h, --help

Распечатывает короткое сообщение об использовании и выйти.

API Python

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

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

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

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

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

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

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

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

Необязательный аргумент filter задаёт функцию обратного вызова, которой передаётся объект Path, представляющий путь к добавляемому файлу (относительно исходного каталога). Он должен возвращает True, если файл должен быть добавлен.

Необязательный аргумент compressed определяет, сжаты ли файлы. Если установлено значение True, файлы в архиве сжимаются методом deflate; в противном случае файлы хранятся в несжатом виде. Данный аргумент не действует при копировании существующего архива.

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

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

Добавлено в версии 3.7: Добавлены аргументы filter и compressed.

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

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

Указание интерпретатора

Обратите внимание, что если вы укажете интерпретатор, а затем распространите архив своего приложения, вам необходимо убедиться, что используемый интерпретатор является переносимым. Средство запуска 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», поскольку вам нужно будет изменить свою шебанг строку, например, для пользователей Python 3.5.

Как правило, вы должны использовать «/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-info, созданные pip, в каталоге myapp. Они содержат метаданные для pip для управления пакетами, и, поскольку вы больше не будете использовать pip, они не требуются. Если вы их оставите, то это не причинит никакого вреда.

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

    $ python -m zipapp -p "interpreter" myapp
    

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

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

Создание исполняемого Windows файла

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

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

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

#define Py_LIMITED_API 1
#include "Python.h"

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#ifdef WINDOWS
int WINAPI wWinMain(
    HINSTANCE hInstance,      /* дескриптор текущего экземпляра */
    HINSTANCE hPrevInstance,  /* дескриптор предыдущего экземпляра */
    LPWSTR lpCmdLine,         /* указатель на командную строку */
    int nCmdShow              /* показать состояние окна */
)
#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, это создаст исполняемый файл GUI, а без него — исполняемый файл консоли.

Чтобы скомпилировать исполняемый файл, вы можете либо просто использовать стандартные инструменты командной строки 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")

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

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

Предостережения

Существуют некоторые ограничения на процесс объединения вашего приложения в один файл. В большинстве, если не во всех, случаях их можно решить без серьезных изменений в приложении.

  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.

Формат архива приложений Python Zip

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

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

Таким образом, формально формат приложения Python-zip таков:

  1. Необязательная строка шебанга, содержащая символы b'#!', за которыми следует имя интерпретатора, а затем символ новой строки (b'\n'). Имя интерпретатора может быть любым, приемлемым для обработки «шебанг» операционной системы или средства запуска Python в Windows. Интерпретатор должен быть закодирован в UTF-8 в Windows и в sys.getfilesystemencoding() в POSIX.
  2. Стандартные данные zip-файла, сгенерированные модулем zipfile. Содержимое zip-файла должно включать файл с именем __main__.py (который должен находиться в «корне» zip-файла, т. е. он не может находиться в подкаталоге). Данные zip-файла могут быть сжаты или несжаты.

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

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