Сборка Python-приложения в бинарник через Nuitka: особенности, проблемы и решения
Введение

Python удобен для разработки, но его интерпретируемая природа создаёт проблемы при распространении. При использовании PyInstaller или аналогичных инструментов требуется, чтобы на целевом компьютере был установлен Python и необходимые зависимости. Nuitka позволяет компилировать Python-код в бинарный исполняемый файл, исключая необходимость в виртуальном окружении. Это повышает производительность и упрощает развёртывание.
Кроме того, Nuitka обеспечивает лучшую защиту от реверс-инжиниринга по сравнению с PyInstaller и cx_Freeze, так как код транслируется в C, а не просто упаковывается в архив с интерпретатором. Это делает его более устойчивым к декомпиляции и анализу. Альтернативным способом защиты может быть использование Cython, который компилирует Python-код в C-расширения, обеспечивая дополнительную защиту, но требует наличия виртуального окружения на целевой системе.
В статье разберём процесс сборки Python-приложения в бинарник через Nuitka, возникшие сложности и их решения.
Nuitka: как это работает
Nuitka транслирует Python-код в C и компилирует через компиляторы (GCC, Clang, MSVC), создавая исполняемый файл, не требующий интерпретатора Python. В отличие от PyInstaller и cx_Freeze, Nuitka выполняет настоящую компиляцию, а не просто упаковку кода. Итоговый бинарник содержит скомпилированные модули и может работать автономно, обеспечивая лучшую производительность, защиту от реверс-инжиниринга и уменьшение зависимости от среды выполнения.
Установка и сборка
Моё приложение разработано с использованием PySide6, так как Nuitka не очень хорошо работает с PyQt. В программе активно используются многопоточность (threading) и SVG-файлы, что накладывает дополнительные требования к сборке.
Использование виртуального окружения и версия python
Для работы использую venv, так как это стандартный инструмент для управления зависимостями в Python. Сам Python 3.12.
Установка Nuitka и зависимостей:
pip install nuitka
brew install clang
или
brew install gcc
Базовая сборка на Windows:
Флаги сборки:
--standalone — создаёт полностью независимый бинарник, включающий все зависимости.
--enable-plugin=pyside6 — включает поддержку PySide6.
--windows-console-mode=disable — отключает консольное окно при запуске GUI-приложения.
--include-package=имя_пакета — вручную включает указанный пакет в сборку.
--include-data-dir=абсолютный_путь=относительный_путь — включает файлы и папки в сборку, где слева указывается полный путь, а справа — путь внутри бинарника.
--onefile — упаковывает весь бинарник и зависимости в один исполняемый файл.
--onedir — создаёт папку с бинарником и всеми зависимостями отдельно.
Пример:
python -m nuitka
--standalone \
--onedir \
--enable-plugin=pyside6 \
--windows-console-mode=disable \
--include-package=Мой класс \
--include-data-dir="папка/папка внутри/данные=папка внутри/папка" \
--include-data-dir="папка/данные=данные" \
--include-data-dir="данные=данные" \
--windows-icon-from-ico="папка/иконка.ico" \
--output-dir="папка/куда будем выводить файл" \
--output-file="Название приложения.exe" \
"путь/Исполняемый код.py"
Сборка на macOS
Флаги сборки для macOS:
--standalone — создаёт автономный бинарник с включёнными зависимостями.
--enable-plugin=pyside6 — поддержка PySide6.
--onefile — создаёт единый исполняемый файл.
--onedir — создаёт папку с бинарником и всеми зависимостями отдельно.
--macos-app-mode — формирует .app-пакет для macOS.
--macos-create-app-bundle — создаёт полноценный macOS-совместимый .app-бандл.
--force-stderr-spec=файл.log — перенаправляет стандартный поток ошибок в указанный файл.
--force-stdout-spec=файл.log — перенаправляет стандартный поток вывода в указанный файл.
Пример команды сборки на macOS:
python -m nuitka \
--standalone \
--onedir \
--enable-plugin=pyside6 \
--macos-app-mode \
--macos-create-app-bundle \
--include-package=Мой_класс \
--include-data-dir="папка/данные=данные" \
--output-dir="папка/куда будем выводить файл" \
--macos-app-icon="иконка.icns" \
--output-file="Название_приложения.app" \
--force-stderr-spec="error.log" \
--force-stdout-spec="output.log" \
"путь/Исполняемый_код.py"
Итоговый результат
После успешной сборки с Nuitka мы получаем полностью автономный бинарник, который можно запускать без установленного Python и дополнительных зависимостей. В зависимости от флагов (--onefile или --onedir), это либо один исполняемый файл, либо папка с бинарником и всеми необходимыми библиотеками.
На Windows получаем две папки: dist и build. В dist находится итоговый исполняемый файл (если используется --onedir).
На macOS я рекомендую использовать только --onedir, далее объясню почему. В результате сборки получаем аналогичные папки dist и build, а также готовое .app-приложение.
После успешной сборки с Nuitka мы получаем полностью автономный бинарник, который можно запускать без установленного Python и дополнительных зависимостей. В зависимости от флагов (--onefile или --onedir), это либо один исполняемый файл, либо папка с бинарником и всеми необходимыми библиотеками.
На Windows это .exe-файл, а на macOS — .app-пакет.
Проблемы и их решения
1. Nuitka не поддерживает PyQt, рекомендуется использовать PySide
PyQt не всегда корректно компилируется в бинарник через Nuitka, поэтому рекомендуется использовать PySide6. Nuitka имеет встроенный плагин для PySide6, который упрощает сборку и уменьшает вероятность ошибок.
2. Проблема с путями к данным в собранном приложении
После сборки бинарника пути к данным могут отличаться от исходных. Для правильной работы с ресурсами можно использовать следующую функцию, которая корректно определяет путь к файлам как в исходном коде, так и в собранном приложении:
import os
import sys
def resource_path(relative_path):
"""Возвращает абсолютный путь к ресурсу, учитывая режим упаковки и macOS .app."""
if sys.platform == "darwin": # macOS
base_path = os.path.abspath(
os.path.join(os.path.dirname(sys.executable), "..", "..", "..")
# здесь можно добавить или убрать ".." в зависимости от глубины вложенности
)
else: # Windows или Linux
if getattr(sys, 'frozen', False):
base_path = os.path.dirname(sys.executable)
else:
base_path = os.path.dirname(os.path.abspath(__file__))
return os.path.join(base_path, relative_path)
Пример использования:
logo_label = QSvgWidget(resource_path("Logo.svg"))
3. Проблема вывода print в macOS .app
Если в приложении есть print-выводы в консоль, то при свёртке в .app на macOS программа не понимает, куда отправлять вывод, и крашится. Самое интересное, что если запускать .app через терминал командой open, программа работает корректно.
Решение: Использование флага --force-stdout-spec=файл.log перенаправляет вывод print в лог-файл, предотвращая падение программы:
--force-stdout-spec=output.log
Заключение
Для завершённого вида на Windows создаём ярлык с путём до исполняемого файла.
На macOS используем команду для создания .dmg-образа:
hdiutil create -fs HFS+ -srcfolder "папка вывода/исполняемый файл" -volname "Название" "Название.dmg"
После этого получаем законченное приложение (особенно на MacOS). Поэтому я и рекомендовал на MacOS делать onedir, ведь при сборе в .dmg все файлы укомплектуются в .app.
В этой статье разобраны основные проблемы, возникающие при сборке Python-приложений в бинарники с помощью Nuitka. Это не полный список возможных сложностей, и если я столкнусь с новыми проблемами, обязательно расскажу о них в следующей статье.