Перейти к содержимому

Pyinstaller как пользоваться linux

  • автор:

pyinstaller. Как к приложению на Linux правильно приделать иконку в угол окна и в панель задач?

Использую PySimpleGui + pyinstaller. Сделал простенькое приложение из одного окошка для Linux, при закуске в углу окна и в панели задаx дефолтная иконка питона, как бы я не старался.

window = sg.Window('', layout, icon='gear.ico', titlebar_icon='gear.ico', #лежит в одном каталоге с главным скриптом background_color=strong_gray, keep_on_top=False ) 

Собираю такой командой:

pyinstaller --onefile --icon='./gear.ico' --add-data './gear.ico:.' main.py 

введите сюда описание изображения

Отслеживать

задан 27 апр 2022 в 5:32

Сергей Попков Сергей Попков

423 3 3 серебряных знака 15 15 бронзовых знаков

А пробовали команду -i, вместо —icon

Использование PyInstaller для простого распространения приложений Python

Для чего используется PyInstaller?

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

Зачем использовать Pyinstaller для пакета Python?

PyInstaller используется для упаковки кода Python в автономные исполняемые приложения для различных операционных систем. Он берет сценарий Python и генерирует один исполняемый файл, который содержит все необходимые зависимости и может быть запущен на компьютерах, на которых не установлен Python. Это позволяет легко распространять и развертывать приложения Python, поскольку пользователю не нужно устанавливать Python и любые необходимые модули в своей системе для запуска приложения. Кроме того, PyInstaller также можно использовать для создания однофайловых исполняемых файлов, которые представляют собой отдельные исполняемые файлы, содержащие все необходимые зависимости для приложения. Это может еще больше упростить распространение приложения, поскольку пользователю нужно загрузить только один файл.

Как установить PyInstaller

PyInstaller доступен как обычный пакет Python. Исходные архивы выпущенных версий доступны по адресуПиПи , но проще установить последнюю версию с помощьюточка:

C:\> pip install pyinstaller

Чтобы обновить существующую установку PyInstaller до последней версии, используйте:

C:\> pip install --upgrade pyinstaller

Чтобы установить текущую версию разработки, используйте:

C:\> pip install https://github.com/pyinstaller/pyinstaller/tarball/

Как создать EXE с помощью PyInstaller

Мы возьмем один файл Python в качестве примера, чтобы подробно объяснить шаги упаковки. Возьмем Python 3.11.0 в качестве примера после установки.aspose.cells.

    Создайте файл примера Python с именемпример.py.

import os from jpype import * __cells_jar_dir__ = os.path.dirname(__file__) addClassPath(os.path.join(__cells_jar_dir__, "aspose-cells-23.1.jar")) addClassPath(os.path.join(__cells_jar_dir__, "bcprov-jdk15on-160.jar")) addClassPath(os.path.join(__cells_jar_dir__, "bcpkix-jdk15on-1.60.jar")) addClassPath(os.path.join(__cells_jar_dir__, "JavaClassBridge.jar")) import jpype import asposecells jpype.startJVM() from asposecells.api import Workbook, FileFormatType, CellsHelper print(CellsHelper.getVersion()) workbook = Workbook(FileFormatType.XLSX) workbook.getWorksheets().get(0).getCells().get("A1").putValue("Hello World") workbook.save("output.xlsx") jpype.shutdownJVM()
C:\app> pyinstaller example.py

дело:image_alt_text

  • Скопируйте файлы jar(aspose-cells-xxx.jar, bcprov-jdk15on-160.jar, bcpkix-jdk15on-1.60.jar, JavaClassBridge.jar. Они находятся в папке C:\Python311\Lib\site-packages\asposecells\lib ) в c:\app.
  • Отредактируйте файл с суффиксом спецификации, чтобы добавить раздел данных, напримерпример.spec.
  • Запустите pyinstaller example.spec в окне командной строки.

    C:\app> pyinstaller example.spec

    Компиляция python кода в exe под Linux

    Если на linux машинах python установлен повсеместно, то windows все наоборот. Решил скомпилировать свое приложения в exe, причем желательно что бы это был одинокий exe файл и не тянул с собой дополнительные dll библиотеки.
    Для компиляции python кода в бинарные файлы есть очень удобная программа pyinstaller.
    Поддержку cross компиляции они уже начали добавлять, но пока она работает не полноценно, поэтому я решил установить python под wine.
    Устанавливаем wine. Качаем python.
    У меня, кстати, python 2.7.2 отказался устанавливать под wine, а вот 2.7.1 встал без проблем.
    Для работы pyinstaller под windows нужен пакет pywin32 , качаем и устанавливаем(http://sourceforge.net/projects/pywin32/.
    Качаем pyinstaller(http://www.pyinstaller.org/.
    После того как у нас установлен python и pywin32 под wine, мы готовы.
    Распаковываем pyinstaller куда-нибудь поближе к установленном python, у меня это было /home/pavel/.wine/drive_c/Python27/progs/pyinstaller-1.5.1
    Туда же, в progs, копируем наше python приложение.
    Для начала надо что pyintaller создал файл конфигурации для нашей системы

    wine python.exe progs/pyinstaller-1.5.1/Configure.py 

    Далее создаем spec файл для нашего python приложения

    wine python.exe progs/pyinstaller-1.5.1/Makespec.py -F progs/foobar.py 

    ключик -F как раз нужен для того чтобы на выходе у нас получился stand-alone executables
    Makespec.py сгенерирует файл foobar.spec , который нам необходим для компиляции.
    И последнее что осталось сделать, это сбилдить наш бинарник.

    wine python.exe progs/pyinstaller-1.5.1/Build.py foobar.spec 

    И в директории dist получаем наш foobar.exe .

    Паковка python-утилит в бинарник

    Пакуем python-код в единый, независимый, standalone бинарник.

    Паковка

    Для того, чтобы любая утилита могла приносить какую-то пользу, её надо доставить конечному пользователю. В контексте python-приложений этот вопрос можно считать не до конца закрытым.

    Например, традиционно принято распространять модули через индекс PyPi. Этот индекс традиционно работает с тремя типами пакетов: source distributions, Wheel и Egg (оба относятся к build distributions). Все эти типы пакетов решают одну и ту же проблему дистрибуции приложения, и не понятно, какой из них лучше.

    Ещё больше проблем возникнет, если мы хотим отдать наше приложение не-python разработчику, или вообще не разработчику: надо установить python определённой версии, установить соответствующий pip, подумать о возможных конфликтах зависимостей и т.д.

    Нетрадиционные варианты

    Рассмотрим нетрадиционные варианты поставки python-приложений. Здесь перечисленны совсем не все (можно извращаться до бесконечности), а только интересные мне:

      VM с установленным приложением и его зависимостями:

      we ship a whole fucking Windows VM to avoid problems with Python depencencies

      • Очень похоже на вариант с VM, но в более тонком виде.
      • Docker так же не везде работает. Примеры: Astra Linux 1.4 и OpenVZ-виртуалки со старым ядром.
      • В теории, самый удобный вариант: достаточно скачать и запустить.
      • Не очевидно, как реализовать.

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

      Паковка python-приложений в бинарный файл.

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

      В простейшем случае можно обойтись bash-скриптом с приписанным в конце tar-архивом:

      Рассмотрим уже готовые реализации паковщиков python-приложений.

      Паковщики

      В недрах Github’а можно найти несчётное количество реализаций паковки python-приложений, но надо выбрать несколько для сравнений. Основные критерии сравнения:

      • Хочется живую реализацию, которая будет поддерживаться и развиваться.
        • Можно определить по времени последнего релиза и популярности в виде звёздочек на github’е.
          • (да, я понимаю, что количетсво звёздочек означает примерно ничего, но помогает как минимум отсортировать варианты)
          • Нас интересует Window, Linux и опционально MacOs.

          Самые интересные находки:

          Название Платформы Релиз
          pyinstaller Win/macOS/Unix 2022 9.5k
          pyoxidizer Win/macOS/Unix 2022 4.2k
          cx-freeze Win/macOS/Unix 2022 956
          py2app macOS 2022 208
          py2exe Win 2022 463
          exxo Unix 2016 460
          bbfreeze Win/macOS/Unix 2014 92

          Под заданные критерии лучше всего подходят pyinstaller и pyoxidizer.

          pyinstaller

          pyinstaller работает как классический self-extracting archive: при запуске бинарника он распаковывает ресурсы в %tmp% , запускает интерпретатор с правильными путями, схлопывается и подчищает за собой мусор. Это влечёт за собой проблемы с производительностью при запуске приложения: каждый раз мучать диск распаковкой ресурсов — дорого.

          Соберём пример приложения с помощью pyinstaller ’а:

           ╰─❯ pip install -U pyinstaller ╰─❯ echo "print('Hello world')" > ./app.py ╰─❯ pyinstaller ./app.py .  ╰─❯ file ./dist/app/app ./dist/app/app: ELF 64-bit LSB executable, x86-64. 

          Проверим производительность полученного бинарного файла, по сравнению с обычным python’ом:

           ╰─❯ hyperfine "./dist/app/app" "python3.8 ./app.py" --warmup 10
          Command Mean [ms] Min [ms] Max [ms] Relative
          ./dist/app/app 22.8 ± 0.9 21.7 25.5 1.65 ± 0.11
          python3.8 ./app.py 13.8 ± 0.8 12.9 17.8 1.00
           Summary 'python3.8 ./app.py' ran 1.65 ± 0.11 times faster than './dist/app/app'

          По результатам заметно, что бинарник, сгенерированный pyinstaller ом в полтора раза медленее обычного интерпретатора python’а.

          pyoxidizer

          PyOxidizer отличается тем, что не распаковывает ресурсы во временную директорию, а использует их прямо из бинарника, включая интерпретатор, что должно обеспечить отличную производительность.

          Соберём тот же самый пример приложения через pyoxidizer:

           ╰─❯ pip install -U pyoxidizer ╰─❯ pyoxidizer init-config-file example ╰─❯ cd example ╰─❯ echo "print('Hello world')" > ./app.py ╰─❯ vim pyoxidizer.bzl // добавить строчку в конец функции make_exe python_config.run_filename = "./app.py" ╰─❯ pyoxidizer build .  ╰─❯ file ./build/x86_64-unknown-linux-gnu/debug/install/example ./build/x86_64-unknown-linux-gnu/debug/install/example: ELF 64-bit LSB shared object, x86-64

          Сравним производительность полученного бинарника с голым интерпретатором:

           ╰─❯ hyperfine "./. /example" "python3.8 ./app.py" --warmup 10
          Command Mean [ms] Min [ms] Max [ms] Relative
          ./. /example 15.2 ± 0.8 14.3 20.5 1.10 ± 0.08
          python3.8 ./app.py 13.8 ± 0.6 12.9 16.9 1.00
           Summary 'python3.8 ./app.py' ran 1.10 ± 0.08 times faster than './build/x86_64-unknown-linux-gnu/debug/install/example'

          Намного лучше, чем с pyinstaller’ом.

          Можно выжать и больше производительности, например в случае с большим количеством импортов. Почитать об этом можно в статье одного из разработчиков pyoxidizer’а, где автор проверял производительность при помощи импортирования почти всей stdlib :

           pyenv installed CPython 3.7.2 # Cold disk cache. $ time ~/.pyenv/versions/3.7.2/bin/python  import_stdlib.py real 0m0.405s user 0m0.165s sys 0m0.065s # Hot disk cache. $ time ~/.pyenv/versions/3.7.2/bin/python  import_stdlib.py real 0m0.193s user 0m0.161s sys 0m0.032s # PyOxidizer with PGO CPython 3.7.2 # Cold disk cache. $ time target/release/pyapp  import_stdlib.py real 0m0.227s user 0m0.145s sys 0m0.016s # Hot disk cache. $ time target/release/pyapp  import_stdlib.py real 0m0.152s user 0m0.136s sys 0m0.016s

          Посмотрим подробнее на PyOxidizer.

          Перекись питона

          PyOxidizer — довольно молодой проект, написанный на модном, стильном, молодёжном rust’е. Состоит из набора модулей, позволяющих встраивать интерпретатор python’а в rust-код, управлять им и паковать ресурсы в бинарный файл.

          Насколько мне известно, развитие встроенного в Rust интерпретатора Python’а началось с пакета inline_python, который позволял инлайнить python-скрипты прямо в rust-код:

           use inline_python::python; fn main()  let who = "world"; let n = 5; python!  for i in range('n): print(i, "Hello", 'who) print("Goodbye") > >

          Под капотом он использует pyo3 — библиотеку для связи python’а и rust’а. Этот же движок используется и в pyembed, который входит в состав pyoxidizer’а и отвечает за управление интерпретатором:

           fn do_it(interpreter: &MainPythonInterpreter) ->  interpreter.with_gil(|py|  match py.eval("print('hello, world')")  Ok(_) => print("python code executed successfully"), Err(e) => print("python error: ", e), > >); >

          oxidized_import — кастомный движок импортов, реализующий загрузку ресурсов (в том числе из памяти), их сканирование и сериализацию.

          Для создания standalone-бинарников используется непосредственно pyoxidizer, комбинирующий в себе pyembed и oxidized_import.

          В документации описано, как это работает. Краткий и очень вольный пересказ: * собирается pyembed с оптимизированным для встраивания бинарником интерпретатора; * собирается архив с исходниками и зависимостями python-утилиты; * из pyembed и архива ресурсов собирается готовый бинарник.

          Загрузка ресурсов

          В стандартном python’е для загрузки ресурсов с диска — например, шаблонов, данных, изображений и etc. — часто используется глобальная переменная __file__ , указывающая на путь к текущему файлу в системе:

           def get_resource(name): """Return a file handle on a named resource next to this module.""" module_dir = os.path.abspath(os.path.dirname(__file__)) resource_path = os.path.join(module_dir, name) return open(resource_path, 'rb')

          С этой переменной есть некоторые проблемы — официальная документация говорит о том, что в определённых случаях __file__ не будет задан: >The pathname of the file from which the module was loaded, if it was loaded from a file. The __file__ attribute may be missing for certain types of modules, such as C modules that are statically linked into the interpreter. For extension modules loaded dynamically from a shared library, it’s the pathname of the shared library file.

          В случае с pyoxidizer’ом, __file__ теряет весь свой смысл, т.к. модули загружаются из памяти. Заметки из документации pyoxidizer’а: >It isn’t clear whether __file__ is actually required and what all is derived from existence of __file__ . It also isn’t clear what __file__ should be set to if it wouldn’t be a concrete filesystem path. Can __file__ be virtual? Can it refer to a binary/archive containing the module?

          По умолчанию, __file__ в pyoxidized-бинарниках не задан, и рекомендуется использовать ResourceAPI (Python 3.7+):

           def get_resource(name): """Return a file handle on a named resource next to this module.""" # get_resource_reader() may not exist or may return None, which this # code doesn't handle. reader = __loader__.get_resource_reader(__name__) return reader.open_resource(name)

          C-расширения

          Pyoxidizer поддерживает использование C-модулей, но с некоторыми пометками:

          • Building C extensions to be embedded in the produced binary works for Windows, Linux, and macOS.
          • Support for extension modules that link additional macOS frameworks not used by Python itself is not yet implemented (but should be easy to do).
          • Support for cross-compiling extension modules (including to MUSL) does not work. (It may appear to work and break at linking or run-time.)
          • We also do not yet provide a build environment for C extensions. So unexpected behavior could occur if e.g. a different compiler toolchain is used to build the C extensions from the one that produced the Python distribution.

          Ещё немного инфы есть в packaging pitfals.

          Дистрибутивы python’а

          Рекомендуются специальные дистрибутивы, подготовленные для максимальной портируемости. У них есть свои проблемы и ограничения, такие как: * Backspace Key Doesn’t work in Python REPL * Windows Static Distributions are Extremely Brittle * Static Linking of musl libc Prevents Extension Module Library Loading * Incompatibility with PyQt on Linux

          PyOxidizer заявляет, что содержит workarounds для них, но не перечисляет конкретно.

          Выводы

          Кратко просуммирую написанное выше:

          • Кроме sdist/bdist есть другие варианты.
          • Запаковать приложение в один файл — реально.
          • PyInstaller — старый и проверенный.
          • PyOxidizer — новый и модный.
            • И быстрый!
            • Можно разобрать на части и использовать по отдельности.
            • Есть некоторые детали при использовании.

            Ссылки

            • Russell Keith-Magee — Keynote — PyCon 2019
            • cx-freeze
            • exxo
            • pyo3
            • py2exe
            • bbfreeze
            • py2app
            • Self-extracting tar
            • PyInstaller
            • PyInstaller — How it works
            • PyOxidizer
            • inline_python
            • Faster In-Memory Python Module Importing
            • PyOxidizer — oxidized_import
            • PyOxidizer — pyembed
            • PyOxidizer — pyoxidizer
            • PyOxidizer — Overview
            • PyOxidizer — Tech notes
            • PyOxidizer — Packaging pitfalls
            • PyOxidizer — Packaging resources
            • PyOxidizer — Status
            • Python standalone
            • Python standalone — Quirks
            • Rust language
  • Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *