PyInstaller собирает в один пакет Python-приложение и все необходимые ему библиотеки следующим образом:
-
считывает файл скрипта
-
анализирует код для выявления всех зависимостей, необходимых для работы
-
создает файл spec, который содержит название скрипта, библиотеки-зависимости, любые файлы, включая те параметры, которые были переданы в команду PyInstaller
-
собирает копии всех библиотек и файлов вместе с активным интерпретатором Python
-
создает папку BUILD в папке со скриптом и записывает логи вместе с рабочими файлами в BUILD
-
создает папку DIST в папке со скриптом, если она еще не существует
-
записывает все необходимые файлы вместе со скриптом или в одну папку, или в один исполняемый файл
-
для установки утилиты PyInstaller следует находясь в виртуальном окружении выполнить команду:
pip install pyinstaller
-
проверить установленную версию PyInstaller можно командой:
pyinstaller --version
-
непосредственно для создания исполняемого файла в командной строке выполняется команда:
pyinstaller --onefile start_constructor.py
Примечание:
- если используется параметр
--onedir
или-D
при генерации исполняемого файла, тогда исполняемый файл и зависимые данные с библиотеками будут помещены в одну папку (это поведение по умолчанию); - если же использовать параметр
--onefile
или-F
, то всё окажется в одном исполняемом файле, то PyInstaller распаковывает все файлы в папку TEMP, выполняет скрипт и удаляет TEMP (Как скомпилировать python-скрипт в exe-файл)
Python создает каталог распространения, который содержит основной исполняемый файл, а также все динамические библиотеки.
После завершения установки в рабочей директории появятся две папки:
BUILD и DIST, а также новый файл с расширением .spec
Название spec-файла будет таким же, как и наименование файла скрипта.
Файл spec — это первый файл, который PyInstaller создает, чтобы закодировать содержимое скрипта Python вместе с передаваемыми при запуске параметрами.
PyInstaller считывает содержимое файла для создания исполняемого файла, определяя всё, что может понадобиться для его запуска.
Файл с расширением .spec по умолчанию сохраняется в текущей директории.
- если используется параметр
-
дополнительные импорты с помощью Hidden Imports
Исполняемому файлу требуются все импорты, которые нужны python-скрипту. Иногда PyInstaller может пропустить динамические импорты или импорты второго уровня, возвращая ошибку ImportError: No module named
Для разрешения этой ошибки необходимо передать название недостающей библиотеки в hidden-import
Например, чтобы добавить библиотеку 'os', в командной строке следует написать команду:
pyinstaller --onefile --hidden-import "os" simple1.py
В нашем случае для правильной сборки исполняемого файла start_constructor также потребовалось использовать опцию 'hidden-import'
Алгоритм:
-
в результате выполнения команды
pyinstaller --onefile start_constructor.py
создаётся файл start_constructor.spec
-
содержимое папок BUILD и DIST необходимо удалить
-
отредактировать файл start_constructor.spec, добавив строки
pathex=[ '.', ], binaries=[], datas=[], hiddenimports=['desktop_constructor_app'],
где атрибут pathex отвечает за путь к папке проекта и значение '.' определяет рабочую директорию для сборки исполняемого файла ('tg_bot_constructor'),
а 'desktop_constructor_app' - импортируемый через опцию hidden-import модуль
-
после редактирования содержимого файла start_constructor.spec выполняется новая сборка исполняемого файла командой:
pyinstaller start_constructor.spec
Примечание: файл start_constructor.spec можно также получить не выполняя сборку с помощью команды
pyi-makespec start_constructor.spec
, в результате выполнения которой создается spec-файл по умолчанию, содержащий все параметры, которые можно указать в командной строке; файл start_constructor.spec создается в текущей директории
-
все требуемые для работы исполняемого файла данные возможно собрать в один бандл с помощью параметра
--add-data
a = Analysis( ['start_constructor.py'], pathex=[ '.', ], binaries=[], datas=[( Path('desktop_constructor_app') / 'constructor_app' / 'translations' / '*.qm', Path('desktop_constructor_app') / 'constructor_app' / 'translations' )], hiddenimports=['desktop_constructor_app'],
Здесь в качестве первого атрибута для объекта класса Analysis передается имя py-файла, который необходимо скомпилировать.
В списке datas указываются файлы, которые требуется загрузить для работы приложения (изображения, музыка/звуки, шрифты).
datas — это список кортежей;
каждый кортеж имеет два элемента строкового типа:
-
1-й — путь до файла, который необходимо загрузить
-
2-й указывает имя папки для хранения файла во время выполнения программы
Примечание: если использовать в виде
a = Analysis(['start_constructor.py'], pathex=['C:/Users/1/Desktop/Python/test'], binaries=[], datas=[('Images/image.png', 'Images'), ('Music/track.mp3', 'Music'), ('font.ttf', '.')], hiddenimports=[],
('.' — означает, что файл будет помещен во временную папку без подкаталога)
Если использовать импортирование
from pathlib import Path
,то использование Path( ) в записи datas позволит унифицировать задание путей для применения с Windows и Linux
В экземпляре класса EXE редактируется: name — имя exe-файла console — отвечает за то, будет ли вызываться консоль при запуске приложения (True) или нет (False) icon — содержит путь к иконке игры
Разъяснения по редактированию путей к файлам в коде
В коде до загрузки файлов необходимо добавить функцию, которая позволит программе найти необходимые для работы файлы независимо от расположения (пояснения по ссылке)
def resource_path(relative): if hasattr(sys, "_MEIPASS"): return os.path.join(sys._MEIPASS, relative) return os.path.join(relative)
При запуске приложения PyInstaller распаковывает данные во временную папку и сохраняет путь к ней в переменной среды _MEIPASS.
Функция resource_path проверяет, создана ли временная папка, и если да, то возвращает путь к ней для дальнейшей загрузки файлов. В противном случае (например, если запустить код через интерпретатор) функция вернет тот путь, который в неё передали (то есть путь к папке проекта с программой).
Чтобы получить путь к файлу
- если файл лежит в той же папке, что и py-файл:
path = resource_path('image.png')
- если файл вынесен в отдельную папку проекта:
path = resource_path(os.path.join('Folder', 'image.png'))
При этом, если файл вынесен в отдельную папку проекта, то и во временной папке нужно создавать соответствующий подкаталог
(см. редактирование параметра datas)
Далее загружаем файл:
img = pygame.image.load(path)
С музыкой и шрифтами аналогично.
-