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.
Примеры¶
Упакуйте каталог в архив и запустите его.
$ 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 в своей системе. Ключом к достижению этой цели является объединение всех зависимостей приложения в архив вместе с кодом приложения.
Для создания отдельного архива необходимо выполнить следующие действия:
Создайте свое приложение в каталоге, как обычно, чтобы у вас был каталог
myapp
, содержащий файл__main__.py
и любой вспомогательный код приложения.Установите все зависимости вашего приложения в каталог
myapp
, используя pip:$ python -m pip install -r requirements.txt --target myapp
(это предполагает, что у вас есть требования к проекту в файле
requirements.txt
- если нет, вы можете просто перечислить зависимости вручную в командной строке pip).Упакуйте приложение с помощью:
$ 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-разрядной).
Предостережения¶
Существуют некоторые ограничения на процесс объединения вашего приложения в один файл. В большинстве случаев, если не во всех, их можно устранить без необходимости внесения серьезных изменений в ваше приложение.
Если ваше приложение зависит от пакета с расширением C, этот пакет нельзя запустить из zip-файла (это ограничение операционной системы, поскольку исполняемый код должен присутствовать в файловой системе, чтобы загрузчик операционной системы мог его загрузить). В этом случае вы можете исключить эту зависимость из zip-файла и либо потребовать, чтобы она была установлена у ваших пользователей, либо отправить ее вместе с вашим zip-файлом и добавить код в ваш
__main__.py
, чтобы включить каталог, содержащий распакованный модуль, вsys.path
. В этом случае вам нужно будет убедиться, что вы отправили соответствующие двоичные файлы для вашей целевой архитектуры (и, возможно, выбрать правильную версию для добавления вsys.path
во время выполнения, в зависимости от компьютера пользователя).Если вы отправляете исполняемый файл Windows, как описано выше, вам необходимо либо убедиться, что у ваших пользователей в пути указан
python3.dll
(что не является стандартным поведением установщика), либо вам следует связать свое приложение со встроенным дистрибутивом.Предложенный выше лаунчер использует 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 является:
Необязательная промежуточная строка, содержащая символы
b'#!'
, за которыми следует имя интерпретатора, а затем символ новой строки (b'\n'
). Имя интерпретатора может быть любым, приемлемым для обработки в операционной системе «shebang» или в программе запуска Python в Windows. Интерпретатор должен быть закодирован в UTF-8 в Windows и вsys.getfilesystemencoding()
в POSIX.Стандартные данные zip-файла, сгенерированные модулем
zipfile
. Содержимое zip-файла должно включать файл с именем__main__.py
(который должен находиться в «корневом каталоге» zip-файла, т.е. он не может находиться в подкаталоге). Данные zip-файла могут быть сжаты или распакованы.
Если в архиве приложения есть строка shebang, в системах POSIX может быть установлен бит исполняемого файла, позволяющий выполнять его напрямую.
Нет требования, чтобы инструменты в этом модуле использовались для создания архивов приложений - модуль удобен, но архивы в вышеуказанном формате, созданные любыми способами, приемлемы для Python.