Вопрос-Ответ

Bundling data files with PyInstaller (--onefile)

Объединение файлов данных с помощью PyInstaller (--onefile)

Я пытаюсь создать однофайловый EXE-файл с помощью PyInstaller, который должен включать изображение и иконку. Я ни за что на свете не смогу заставить его работать с --onefile.

Если я это сделаю, --onedir все работает очень хорошо. Когда я использую --onefile, он не может найти указанные дополнительные файлы (при запуске скомпилированного EXE-файла). Он находит библиотеки DLL и все остальное нормально, только не два изображения.

Я заглянул во временный каталог, созданный при запуске EXE (\Temp\_MEI95642\ например), и файлы действительно там есть. Когда я помещаю EXE в этот временный каталог, он их находит. Очень запутанно.

Это то, что я добавил в .spec файл

a.datas += [('images/icon.ico', 'D:\\[workspace]\\App\\src\\images\\icon.ico',  'DATA'),
('images/loaderani.gif','D:\\[workspace]\\App\\src\\images\\loaderani.gif','DATA')]

Я должен добавить, что я также пытался не помещать их во вложенные папки, но это ничего не изменило.

Редактировать: Отмечен новый ответ как правильный из-за обновления PyInstaller.

Переведено автоматически
Ответ 1

Более новые версии PyInstaller больше не устанавливают env переменную, поэтому отличный ответ Ши не будет работать. Теперь путь устанавливается как sys._MEIPASS:

def resource_path(relative_path):
""" Get absolute path to resource, works for dev and for PyInstaller """
try:
# PyInstaller creates a temp folder and stores path in _MEIPASS
base_path = sys._MEIPASS
except Exception:
base_path = os.path.abspath(".")

return os.path.join(base_path, relative_path)
Ответ 2

pyinstaller распаковывает ваши данные во временную папку и сохраняет путь к этому каталогу в _MEIPASS2 переменной окружения. Чтобы получить _MEIPASS2 каталог в упакованном режиме и использовать локальный каталог в распакованном режиме (разработки), я использую это:

def resource_path(relative):
return os.path.join(
os.environ.get(
"_MEIPASS2",
os.path.abspath(".")
),
relative
)

Вывод:

# in development
>>> resource_path("app_icon.ico")
"/home/shish/src/my_app/app_icon.ico"

# in production
>>> resource_path("app_icon.ico")
"/tmp/_MEI34121/app_icon.ico"
Ответ 3

Во всех остальных ответах используется текущий рабочий каталог в случае, когда приложение не PyInstalled (т.е. sys._MEIPASS Не установлено). Это неправильно, поскольку не позволяет вам запускать ваше приложение из каталога, отличного от того, в котором находится ваш скрипт.

Лучшее решение:

import sys
import os

def resource_path(relative_path):
""" Get absolute path to resource, works for dev and for PyInstaller """
base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
return os.path.join(base_path, relative_path)
Ответ 4

Я занимался этой проблемой долгое (ну, очень долгое) время. Я перерыл почти все источники, но в моей голове ничего не складывалось.

Наконец, я думаю, что выяснил точные шаги, которым нужно следовать, и хотел поделиться.

Обратите внимание, что в моем ответе используется информация об ответах других пользователей на этот вопрос.

Как создать автономный исполняемый файл проекта python.

Предположим, у нас есть project_folder и дерево файлов выглядит следующим образом:


project_folder/
main.py
xxx.py # another module
yyy.py # another module
sound/ # directory containing the sound files
img/ # directory containing the image files
venv/ # if using a venv

Прежде всего, предположим, что вы определили пути к sound/ и img/ папкам в переменные sound_dir и img_dir следующим образом:

img_dir = os.path.join(os.path.dirname(__file__), "img")
sound_dir = os.path.join(os.path.dirname(__file__), "sound")

Вы должны изменить их следующим образом:

img_dir = resource_path("img")
sound_dir = resource_path("sound")

Где, resource_path() определяется в верхней части вашего скрипта как:

def resource_path(relative_path):
""" Get absolute path to resource, works for dev and for PyInstaller """
base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
return os.path.join(base_path, relative_path)

Активируйте виртуальный env при использовании venv,

Установите pyinstaller, если вы этого еще не сделали, с помощью: pip3 install pyinstaller.

Запустите: pyi-makespec --onefile main.py чтобы создать файл спецификации для процесса компиляции и сборки.

Это изменит иерархию файлов на:


project_folder/
main.py
xxx.py # modules
xxx.py # modules
sound/ # directory containing the sound files
img/ # directory containing the image files
venv/ # if using a venv
main.spec

Открыть (с помощью редактора) main.spec:

В верхней части файла вставьте:

added_files = [

("sound", "sound"),
("img", "img")

]

Затем измените строку datas=[], на datas=added_files,

Подробнее о выполняемых операциях main.spec смотрите Здесь.

Выполнить pyinstaller --onefile main.spec

И это все, вы можете запускать main в project_folder/dist из любого места, не имея ничего другого в своей папке. Вы можете распространять только этот main файл. Теперь это настоящий автономный файл.

python