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