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

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

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


Модуль предоставляет инструменты для управления созданием 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

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

Python API

Модуль определяет две удобные функции:

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. При создании архива из каталога, если целевой объект является файловым объектом, он будет передан классу zip-файла.zip-файла и должен предоставить методы, необходимые этому классу.

Добавлено в версии 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, если требуется имя команды «plain». В Windows файл myapp.pyz[w] является исполняемым благодаря тому, что Python интерпретатор регистрирует расширения файлов .pyz и .pyzw при установке.

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

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

Поэтому в Windows часто предпочтительно создавать исполняемый файл из zipapp. Это относительно легко, хотя и требует компилятор C. Основной подход основан на том факте, что zip-файлаs могут иметь произвольные данные, а файлы 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)
>>>     # Теперь исполняемый файл графического интерфейса
>>>     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 бит).

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

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

  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

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

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

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