# `zipapp` — Manage executable Python zip archives

Нове в версії 3.5.

Вихідний код: Lib/zipapp.py

Цей модуль надає інструменти для керування створенням zip-файлів, що містять код Python, який можна виконувати безпосередньо інтерпретатором Python. Модуль забезпечує як Інтерфейс командного рядка, так і 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. Якщо джерело є файлом, це має бути архів, і його буде скопійовано до цільового архіву (або буде показано вміст його рядка shebang, якщо вказано параметр –info).

Розуміються такі варіанти:

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

  Запишіть вихідні дані у файл з назвою output. Якщо цей параметр не вказано, ім’я вихідного файлу буде таким самим, як і вхідне джерело, із доданим розширенням `.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

  Надрукуйте коротке повідомлення про використання та вийдіть.

## 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`.

  Аргумент інтерпретатор визначає ім’я інтерпретатора 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; інакше файли зберігаються нестисненими. Цей аргумент не діє під час копіювання існуючого архіву.

  Якщо для source або target указано файловий об’єкт, закрити його після виклику create_archive несе абонент, що викликає.

  Під час копіювання існуючого архіву наданим файловим об’єктам потрібні лише методи `read` і `readline` або `write`. Під час створення архіву з каталогу, якщо метою є файловий об’єкт, він буде переданий до класу `zipfile.ZipFile` і повинен надати методи, необхідні цьому класу.

  Змінено в версії 3.7: Added the filter and compressed parameters.

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

To update the file in place, do the replacement in memory using a `BytesIO` object, and then overwrite the source afterwards. Note that there is a risk when overwriting a file in place that an error will result in the loss of the original file. This code does not protect against such errors, but production code should do so. Also, this method will only work if the archive fits in memory:

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

## Створення автономних програм за допомогою zipap

Використовуючи модуль `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` під час встановлення.

### Making a Windows executable

On Windows, registration of the `.pyz` extension is optional, and furthermore, there are certain places that don’t recognise registered extensions «transparently» (the simplest example is that `subprocess.run(['myapp'])` won’t find your application - you need to explicitly specify the extension).

On Windows, therefore, it is often preferable to create an executable from the zipapp. This is relatively easy, although it does require a C compiler. The basic approach relies on the fact that zipfiles can have arbitrary data prepended, and Windows exe files can have arbitrary data appended. So by creating a suitable launcher and tacking the `.pyz` file onto the end of it, you end up with a single-file executable that runs your application.

A suitable launcher can be as simple as the following:

    #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);
    }

If you define the `WINDOWS` preprocessor symbol, this will generate a GUI executable, and without it, a console executable.

To compile the executable, you can either just use the standard MSVC command line tools, or you can take advantage of the fact that distutils knows how to compile Python source:

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

The resulting launcher uses the «Limited ABI», so it will run unchanged with any version of Python 3.x. All it needs is for Python (`python3.dll`) to be on the user’s `PATH`.

For a fully standalone distribution, you can distribute the launcher with your application appended, bundled with the Python «embedded» distribution. This will run on any PC with the appropriate architecture (32 bit or 64 bit).

### Застереження

There are some limitations to the process of bundling your application into a single file. In most, if not all, cases they can be addressed without needing major changes to your application.

1. Якщо ваша програма залежить від пакета, який містить розширення C, цей пакет не можна запустити з zip-файлу (це обмеження ОС, оскільки виконуваний код має бути присутнім у файловій системі, щоб завантажувач ОС міг його завантажити). У цьому випадку ви можете виключити цю залежність із zip-файлу та вимагати, щоб ваші користувачі встановили його, або надіслати його разом із zip-файлом і додати код до свого `__main__.py`, щоб включити каталог, що містить розархівований модуль, у `sys.path`. У цьому випадку вам потрібно буде переконатися, що надіслано відповідні двійкові файли для вашої цільової архітектури (і, можливо, вибрати правильну версію для додавання до `sys.path` під час виконання, на основі комп’ютера користувача).

2. If you are shipping a Windows executable as described above, you either need to ensure that your users have `python3.dll` on their PATH (which is not the default behaviour of the installer) or you should bundle your application with the embedded distribution.

3. The suggested launcher above uses the Python embedding API. This means that in your application, `sys.executable` will be your application, and not a conventional Python interpreter. Your code and its dependencies need to be prepared for this possibility. For example, if your application uses the `multiprocessing` module, it will need to call `multiprocessing.set_executable()` to let the module know where to find the standard Python interpreter.

## Формат архіву програми 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. An optional shebang line, containing the characters `b'#!'` followed by an interpreter name, and then a newline (`b'\n'`) character. The interpreter name can be anything acceptable to the OS «shebang» processing, or the Python launcher on Windows. The interpreter should be encoded in UTF-8 on Windows, and in `sys.getfilesystemencoding()` on POSIX.

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

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

Немає вимог, щоб інструменти в цьому модулі використовувалися для створення архівів програми - модуль є зручним, але архіви у вищезазначеному форматі, створені будь-якими засобами, прийнятні для Python.