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

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

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


Этот модуль предоставляет инструменты для управления созданием zip-файлов, содержащих код Python, который может быть executed directly by the Python interpreter. Модуль предоставляет как Интерфейс командной строки, так и API Python.

Базовый пример

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

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

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

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

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

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

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

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

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

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

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

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

Аргумент main указывает имя вызываемого объекта, который будет использоваться в качестве основной программы для архива. Его можно указать только в том случае, если источником является каталог, и этот источник еще не содержит __main__.py файла. Аргумент main должен иметь вид «pkg.module:вызываемый», и архив будет запущен путем импорта «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>

Чтобы заменить строку 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. Упакуйте приложение с помощью:

    $ python -m zipapp -p "interpreter" myapp
    

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

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

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

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

Поэтому в Windows часто предпочтительнее создавать исполняемый файл из приложения zip. Это относительно просто, хотя и требует использования компилятора 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,      /* 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")

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

  3. Предложенный выше лаунчер использует Python embedding API. Это означает, что в вашем приложении 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).

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

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

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

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

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

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