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

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

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


Этот модуль предоставляет инструменты для управления созданием zip-файлов, содержащих код Python, который может быть executed directly by the Python interpreter. Модуль предоставляет как Интерфейс командной строки, так и 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 - это файл, то это должен быть архив, и он будет скопирован в целевой архив (или будет показано содержимое его строки shebang, если указана опция –info).

Понимаются следующие варианты:

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

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

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

-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

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

-h, --help

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

Python API

В модуле определены две удобные функции:

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

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

  • Имя каталога или path-like object, ссылающееся на каталог, в этом случае новый архив приложения будет создан из содержимого этого каталога.

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

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

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

  • Если это имя файла или path-like object, то архив будет записан в этот файл.

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

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

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

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

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

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

Если для источника или цели указан объект файла, вызывающая сторона обязана закрыть его после вызова 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>

Чтобы заменить строку 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())

Определение интерпретатора

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

Создание автономных приложений с помощью 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-файлы можно добавлять произвольные данные, а в exe-файлы 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,      /* 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'))
>>>     # 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")

Полученная программа запуска использует «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 Zip

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

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

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

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

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

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

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

Вернуться на верх